mirror of
https://github.com/nasa/openmct.git
synced 2025-06-29 12:13:03 +00:00
Compare commits
9 Commits
plot-sync
...
v1-feature
Author | SHA1 | Date | |
---|---|---|---|
00abbf712b | |||
e34e3210d4 | |||
5e9d9d390f | |||
2ad629aae0 | |||
3416af2d68 | |||
19f210dc30 | |||
828a272ae1 | |||
401e0c69d7 | |||
442abea8ae |
@ -21,6 +21,5 @@
|
|||||||
"shadow": "outer",
|
"shadow": "outer",
|
||||||
"strict": "implied",
|
"strict": "implied",
|
||||||
"undef": true,
|
"undef": true,
|
||||||
"unused": "vars",
|
"unused": "vars"
|
||||||
"latedef": "nofunc"
|
|
||||||
}
|
}
|
||||||
|
15
API.md
15
API.md
@ -879,21 +879,6 @@ openmct.install(openmct.plugins.CouchDB('http://localhost:9200'))
|
|||||||
* `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different
|
* `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different
|
||||||
themes (dark and light) available for Open MCT. Note that at least one
|
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.
|
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
|
* `openmct.plugins.LocalStorage` provides persistence of user-created
|
||||||
objects in browser-local storage. This is particularly useful in
|
objects in browser-local storage. This is particularly useful in
|
||||||
development environments.
|
development environments.
|
||||||
|
@ -88,7 +88,7 @@ and [`gulp`](http://gulpjs.com/).
|
|||||||
|
|
||||||
To build Open MCT for deployment:
|
To build Open MCT for deployment:
|
||||||
|
|
||||||
`npm run prepare`
|
`npm run prepublish`
|
||||||
|
|
||||||
This will compile and minify JavaScript sources, as well as copy over assets.
|
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
|
The contents of the `dist` folder will contain a runnable Open MCT
|
||||||
|
@ -1,11 +1,3 @@
|
|||||||
machine:
|
|
||||||
node:
|
|
||||||
version: 4.7.0
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
pre:
|
|
||||||
- npm install -g npm@latest
|
|
||||||
|
|
||||||
deployment:
|
deployment:
|
||||||
production:
|
production:
|
||||||
branch: master
|
branch: master
|
||||||
|
@ -2283,7 +2283,7 @@ To install build dependencies (only needs to be run once):
|
|||||||
|
|
||||||
To build:
|
To build:
|
||||||
|
|
||||||
`npm run prepare`
|
`npm run prepublish`
|
||||||
|
|
||||||
This will compile and minify JavaScript sources, as well as copy over assets.
|
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
|
The contents of the `dist` folder will contain a runnable Open MCT
|
||||||
|
@ -1,121 +0,0 @@
|
|||||||
# Security Guide
|
|
||||||
|
|
||||||
Open MCT is a rich client with plugin support that executes as a single page
|
|
||||||
web application in a browser environment. Security concerns and
|
|
||||||
vulnerabilities associated with the web as a platform should be considered
|
|
||||||
before deploying Open MCT (or any other web application) for mission or
|
|
||||||
production usage.
|
|
||||||
|
|
||||||
This document describes several important points to consider when developing
|
|
||||||
for or deploying Open MCT securely. Other resources such as
|
|
||||||
[Open Web Application Security Project (OWASP)](https://www.owasp.org)
|
|
||||||
provide a deeper and more general overview of security for web applications.
|
|
||||||
|
|
||||||
|
|
||||||
## Security Model
|
|
||||||
|
|
||||||
Open MCT has been architected assuming the following deployment pattern:
|
|
||||||
|
|
||||||
* A tagged, tested Open MCT version will be used.
|
|
||||||
* Externally authored plugins will be installed.
|
|
||||||
* A server will provide persistent storage, telemetry, and other shared data.
|
|
||||||
* Authorization, authentication, and auditing will be handled by a server.
|
|
||||||
|
|
||||||
|
|
||||||
## Security Procedures
|
|
||||||
|
|
||||||
The Open MCT team secures our code base using a combination of code review,
|
|
||||||
dependency review, and periodic security reviews. Static analysis performed
|
|
||||||
during automated verification additionally safeguards against common
|
|
||||||
coding errors which may result in vulnerabilities.
|
|
||||||
|
|
||||||
|
|
||||||
### Code Review
|
|
||||||
|
|
||||||
All contributions are reviewed by internal team members. External
|
|
||||||
contributors receive increased scrutiny for security and quality,
|
|
||||||
and must sign a licensing agreement.
|
|
||||||
|
|
||||||
### Dependency Review
|
|
||||||
|
|
||||||
Before integrating third-party dependencies, they are reviewed for security
|
|
||||||
and quality, with consideration given to authors and users of these
|
|
||||||
dependencies, as well as review of open source code.
|
|
||||||
|
|
||||||
### Periodic Security Reviews
|
|
||||||
|
|
||||||
Open MCT's code, design, and architecture are periodically reviewed
|
|
||||||
(approximately annually) for common security issues, such as the
|
|
||||||
[OWASP Top Ten](https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project).
|
|
||||||
|
|
||||||
|
|
||||||
## Security Concerns
|
|
||||||
|
|
||||||
Certain security concerns deserve special attention when deploying Open MCT,
|
|
||||||
or when authoring plugins.
|
|
||||||
|
|
||||||
### Identity Spoofing
|
|
||||||
|
|
||||||
Open MCT issues calls to web services with the privileges of a logged in user.
|
|
||||||
Compromised sources (either for Open MCT itself or a plugin) could
|
|
||||||
therefore allow malicious code to execute with those privileges.
|
|
||||||
|
|
||||||
To avoid this:
|
|
||||||
|
|
||||||
* Serve Open MCT and other scripts over SSL (https rather than http)
|
|
||||||
to prevent man-in-the-middle attacks.
|
|
||||||
* Exercise precautions such as security reviews for any plugins or
|
|
||||||
applications built for or with Open MCT to reject malicious changes.
|
|
||||||
|
|
||||||
### Information Disclosure
|
|
||||||
|
|
||||||
If Open MCT is used to handle or display sensitive data, any components
|
|
||||||
(such as adapter plugins) must take care to avoid leaking or disclosing
|
|
||||||
this information. For example, avoid sending sensitive data to third-party
|
|
||||||
servers or insecure APIs.
|
|
||||||
|
|
||||||
### Data Tampering
|
|
||||||
|
|
||||||
The web application architecture leaves open the possibility that direct
|
|
||||||
calls will be made to back-end services, circumventing Open MCT entirely.
|
|
||||||
As such, Open MCT assumes that server components will perform any necessary
|
|
||||||
data validation during calls issues to the server.
|
|
||||||
|
|
||||||
Additionally, plugins which serialize and write data to the server must
|
|
||||||
escape that data to avoid database injection attacks, and similar.
|
|
||||||
|
|
||||||
### Repudiation
|
|
||||||
|
|
||||||
Open MCT assumes that servers log any relevant interactions and associates
|
|
||||||
these with a user identity; the specific user actions taken within the
|
|
||||||
application are assumed not to be of concern for auditing.
|
|
||||||
|
|
||||||
In the absence of server-side logging, users may disclaim (maliciously,
|
|
||||||
mistakenly, or otherwise) actions taken within the system without any
|
|
||||||
way to prove otherwise.
|
|
||||||
|
|
||||||
If keeping client-level interactions is important, this will need to be
|
|
||||||
implemented via a plugin.
|
|
||||||
|
|
||||||
### Denial-of-service
|
|
||||||
|
|
||||||
Open MCT assumes that server-side components will be insulated against
|
|
||||||
denial-of-service attacks. Services should only permit resource-intensive
|
|
||||||
tasks to be initiated by known or trusted users.
|
|
||||||
|
|
||||||
### Elevation of Privilege
|
|
||||||
|
|
||||||
Corollary to the assumption that servers guide against identity spoofing,
|
|
||||||
Open MCT assumes that services do not allow a user to act with
|
|
||||||
inappropriately escalated privileges. Open MCT cannot protect against
|
|
||||||
such escalation; in the clearest case, a malicious actor could interact
|
|
||||||
with web services directly to exploit such a vulnerability.
|
|
||||||
|
|
||||||
## Additional Reading
|
|
||||||
|
|
||||||
The following resources have been used as a basis for identifying potential
|
|
||||||
security threats to Open MCT deployments in preparation of this document:
|
|
||||||
|
|
||||||
* [STRIDE model](https://www.owasp.org/index.php/Threat_Risk_Modeling#STRIDE)
|
|
||||||
* [Attack Surface Analysis Cheat Sheet](https://www.owasp.org/index.php/Attack_Surface_Analysis_Cheat_Sheet)
|
|
||||||
* [XSS Prevention Cheat Sheet](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet)
|
|
@ -30,8 +30,7 @@ define([
|
|||||||
amplitude: 1,
|
amplitude: 1,
|
||||||
period: 10,
|
period: 10,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
dataRateInHz: 1,
|
dataRateInHz: 1
|
||||||
phase: 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function GeneratorProvider() {
|
function GeneratorProvider() {
|
||||||
@ -51,8 +50,7 @@ define([
|
|||||||
'amplitude',
|
'amplitude',
|
||||||
'period',
|
'period',
|
||||||
'offset',
|
'offset',
|
||||||
'dataRateInHz',
|
'dataRateInHz'
|
||||||
'phase',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
request = request || {};
|
request = request || {};
|
||||||
@ -63,7 +61,7 @@ define([
|
|||||||
if (domainObject.telemetry && domainObject.telemetry.hasOwnProperty(prop)) {
|
if (domainObject.telemetry && domainObject.telemetry.hasOwnProperty(prop)) {
|
||||||
workerRequest[prop] = domainObject.telemetry[prop];
|
workerRequest[prop] = domainObject.telemetry[prop];
|
||||||
}
|
}
|
||||||
if (request && request.hasOwnProperty(prop)) {
|
if (request.hasOwnProperty(prop)) {
|
||||||
workerRequest[prop] = request[prop];
|
workerRequest[prop] = request[prop];
|
||||||
}
|
}
|
||||||
if (!workerRequest[prop]) {
|
if (!workerRequest[prop]) {
|
||||||
@ -71,7 +69,7 @@ define([
|
|||||||
}
|
}
|
||||||
workerRequest[prop] = Number(workerRequest[prop]);
|
workerRequest[prop] = Number(workerRequest[prop]);
|
||||||
});
|
});
|
||||||
workerRequest.name = domainObject.name;
|
|
||||||
return workerRequest;
|
return workerRequest;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
|
|
||||||
], function (
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
function StateGeneratorProvider() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function pointForTimestamp(timestamp, duration, name) {
|
|
||||||
return {
|
|
||||||
name: name,
|
|
||||||
utc: Math.floor(timestamp / duration) * duration,
|
|
||||||
value: Math.floor(timestamp / duration) % 2
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
|
|
||||||
return domainObject.type === 'example.state-generator';
|
|
||||||
};
|
|
||||||
|
|
||||||
StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
|
||||||
var duration = domainObject.telemetry.duration * 1000;
|
|
||||||
|
|
||||||
var interval = setInterval(function () {
|
|
||||||
var now = Date.now();
|
|
||||||
callback(pointForTimestamp(now, duration, domainObject.name));
|
|
||||||
}, duration);
|
|
||||||
|
|
||||||
return function () {
|
|
||||||
clearInterval(interval);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
|
|
||||||
return domainObject.type === 'example.state-generator';
|
|
||||||
};
|
|
||||||
|
|
||||||
StateGeneratorProvider.prototype.request = function (domainObject, options) {
|
|
||||||
var start = options.start;
|
|
||||||
var end = options.end;
|
|
||||||
var duration = domainObject.telemetry.duration * 1000;
|
|
||||||
if (options.strategy === 'latest' || options.size === 1) {
|
|
||||||
start = end;
|
|
||||||
}
|
|
||||||
var data = [];
|
|
||||||
while (start <= end && data.length < 5000) {
|
|
||||||
data.push(pointForTimestamp(start, duration, domainObject.name));
|
|
||||||
start += 5000;
|
|
||||||
}
|
|
||||||
return Promise.resolve(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
return StateGeneratorProvider;
|
|
||||||
|
|
||||||
});
|
|
@ -44,7 +44,9 @@ define([
|
|||||||
message = message.data;
|
message = message.data;
|
||||||
var callback = this.callbacks[message.id];
|
var callback = this.callbacks[message.id];
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(message);
|
if (callback(message)) {
|
||||||
|
delete this.callbacks[message.id];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -70,7 +72,6 @@ define([
|
|||||||
deferred.resolve = resolve;
|
deferred.resolve = resolve;
|
||||||
deferred.reject = reject;
|
deferred.reject = reject;
|
||||||
});
|
});
|
||||||
var messageId;
|
|
||||||
|
|
||||||
function callback(message) {
|
function callback(message) {
|
||||||
if (message.error) {
|
if (message.error) {
|
||||||
@ -78,27 +79,33 @@ define([
|
|||||||
} else {
|
} else {
|
||||||
deferred.resolve(message.data);
|
deferred.resolve(message.data);
|
||||||
}
|
}
|
||||||
delete this.callbacks[messageId];
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
messageId = this.dispatch('request', request, callback.bind(this));
|
this.dispatch('request', request, callback);
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
WorkerInterface.prototype.subscribe = function (request, cb) {
|
WorkerInterface.prototype.subscribe = function (request, cb) {
|
||||||
function callback(message) {
|
var isCancelled = false;
|
||||||
|
|
||||||
|
var callback = function (message) {
|
||||||
|
if (isCancelled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
cb(message.data);
|
cb(message.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
var messageId = this.dispatch('subscribe', request, callback);
|
var messageId = this.dispatch('subscribe', request, callback)
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
|
isCancelled = true;
|
||||||
this.dispatch('unsubscribe', {
|
this.dispatch('unsubscribe', {
|
||||||
id: messageId
|
id: messageId
|
||||||
});
|
});
|
||||||
delete this.callbacks[messageId];
|
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,11 +62,10 @@
|
|||||||
self.postMessage({
|
self.postMessage({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
data: {
|
data: {
|
||||||
name: data.name,
|
|
||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60*60*24*1000,
|
yesterday: nextStep - 60*60*24*1000,
|
||||||
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase),
|
sin: sin(nextStep, data.period, data.amplitude, data.offset),
|
||||||
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase)
|
cos: cos(nextStep, data.period, data.amplitude, data.offset)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
nextStep += step;
|
nextStep += step;
|
||||||
@ -83,22 +82,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onRequest(message) {
|
function onRequest(message) {
|
||||||
var request = message.data;
|
var data = message.data;
|
||||||
if (request.end == undefined) {
|
if (data.end == undefined) {
|
||||||
request.end = Date.now();
|
data.end = Date.now();
|
||||||
}
|
}
|
||||||
if (request.start == undefined){
|
if (data.start == undefined){
|
||||||
request.start = request.end - FIFTEEN_MINUTES;
|
data.start = data.end - FIFTEEN_MINUTES;
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
var start = request.start;
|
var start = data.start;
|
||||||
var end = request.end > now ? now : request.end;
|
var end = data.end > now ? now : data.end;
|
||||||
var amplitude = request.amplitude;
|
var amplitude = data.amplitude;
|
||||||
var period = request.period;
|
var period = data.period;
|
||||||
var offset = request.offset;
|
var offset = data.offset;
|
||||||
var dataRateInHz = request.dataRateInHz;
|
var dataRateInHz = data.dataRateInHz;
|
||||||
var phase = request.phase;
|
|
||||||
|
|
||||||
var step = 1000 / dataRateInHz;
|
var step = 1000 / dataRateInHz;
|
||||||
var nextStep = start - (start % step) + step;
|
var nextStep = start - (start % step) + step;
|
||||||
@ -107,11 +105,10 @@
|
|||||||
|
|
||||||
for (; nextStep < end && data.length < 5000; nextStep += step) {
|
for (; nextStep < end && data.length < 5000; nextStep += step) {
|
||||||
data.push({
|
data.push({
|
||||||
name: request.name,
|
|
||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60*60*24*1000,
|
yesterday: nextStep - 60*60*24*1000,
|
||||||
sin: sin(nextStep, period, amplitude, offset, phase),
|
sin: sin(nextStep, period, amplitude, offset),
|
||||||
cos: cos(nextStep, period, amplitude, offset, phase)
|
cos: cos(nextStep, period, amplitude, offset)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
@ -120,14 +117,14 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function cos(timestamp, period, amplitude, offset, phase) {
|
function cos(timestamp, period, amplitude, offset) {
|
||||||
return amplitude *
|
return amplitude *
|
||||||
Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
|
Math.cos(timestamp / period / 1000 * Math.PI * 2) + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sin(timestamp, period, amplitude, offset, phase) {
|
function sin(timestamp, period, amplitude, offset) {
|
||||||
return amplitude *
|
return amplitude *
|
||||||
Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
|
Math.sin(timestamp / period / 1000 * Math.PI * 2) + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendError(error, message) {
|
function sendError(error, message) {
|
||||||
|
@ -23,12 +23,10 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
"./GeneratorProvider",
|
"./GeneratorProvider",
|
||||||
"./SinewaveLimitCapability",
|
"./SinewaveLimitCapability"
|
||||||
"./StateGeneratorProvider"
|
|
||||||
], function (
|
], function (
|
||||||
GeneratorProvider,
|
GeneratorProvider,
|
||||||
SinewaveLimitCapability,
|
SinewaveLimitCapability
|
||||||
StateGeneratorProvider
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var legacyExtensions = {
|
var legacyExtensions = {
|
||||||
@ -48,75 +46,6 @@ define([
|
|||||||
openmct.legacyExtension(type, extension)
|
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", {
|
openmct.types.addType("generator", {
|
||||||
name: "Sine Wave Generator",
|
name: "Sine Wave Generator",
|
||||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||||
@ -170,18 +99,6 @@ define([
|
|||||||
"dataRateInHz"
|
"dataRateInHz"
|
||||||
],
|
],
|
||||||
pattern: "^\\d*(\\.\\d*)?$"
|
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) {
|
initialize: function (object) {
|
||||||
@ -190,12 +107,7 @@ define([
|
|||||||
amplitude: 1,
|
amplitude: 1,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
dataRateInHz: 1,
|
dataRateInHz: 1,
|
||||||
phase: 0,
|
|
||||||
values: [
|
values: [
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
name: "Name"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "utc",
|
key: "utc",
|
||||||
name: "Time",
|
name: "Time",
|
||||||
@ -230,7 +142,6 @@ define([
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
openmct.telemetry.addProvider(new GeneratorProvider());
|
openmct.telemetry.addProvider(new GeneratorProvider());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,9 +48,8 @@ define([
|
|||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
||||||
];
|
];
|
||||||
|
|
||||||
function pointForTimestamp(timestamp, name) {
|
function pointForTimestamp(timestamp) {
|
||||||
return {
|
return {
|
||||||
name: name,
|
|
||||||
utc: Math.floor(timestamp / 5000) * 5000,
|
utc: Math.floor(timestamp / 5000) * 5000,
|
||||||
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
||||||
};
|
};
|
||||||
@ -62,7 +61,7 @@ define([
|
|||||||
},
|
},
|
||||||
subscribe: function (domainObject, callback) {
|
subscribe: function (domainObject, callback) {
|
||||||
var interval = setInterval(function () {
|
var interval = setInterval(function () {
|
||||||
callback(pointForTimestamp(Date.now(), domainObject.name));
|
callback(pointForTimestamp(Date.now()));
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
return function (interval) {
|
return function (interval) {
|
||||||
@ -80,8 +79,8 @@ define([
|
|||||||
var start = options.start;
|
var start = options.start;
|
||||||
var end = options.end;
|
var end = options.end;
|
||||||
var data = [];
|
var data = [];
|
||||||
while (start <= end && data.length < 5000) {
|
while (start < end && data.length < 5000) {
|
||||||
data.push(pointForTimestamp(start, domainObject.name));
|
data.push(pointForTimestamp(start));
|
||||||
start += 5000;
|
start += 5000;
|
||||||
}
|
}
|
||||||
return Promise.resolve(data);
|
return Promise.resolve(data);
|
||||||
@ -94,7 +93,7 @@ define([
|
|||||||
options.strategy === 'latest';
|
options.strategy === 'latest';
|
||||||
},
|
},
|
||||||
request: function (domainObject, options) {
|
request: function (domainObject, options) {
|
||||||
return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name)]);
|
return Promise.resolve([pointForTimestamp(Date.now())]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -110,10 +109,6 @@ define([
|
|||||||
initialize: function (object) {
|
initialize: function (object) {
|
||||||
object.telemetry = {
|
object.telemetry = {
|
||||||
values: [
|
values: [
|
||||||
{
|
|
||||||
name: 'Name',
|
|
||||||
key: 'name'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Time',
|
name: 'Time',
|
||||||
key: 'utc',
|
key: 'utc',
|
||||||
|
@ -58,7 +58,11 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-mct-example > div { margin-bottom: $interiorMarginLg; }
|
.w-mct-example {
|
||||||
|
div {
|
||||||
|
margin-bottom: $interiorMarginLg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
code,
|
code,
|
||||||
pre {
|
pre {
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
<h2>Palettes</h2>
|
<h2>Palettes</h2>
|
||||||
<div class="cols cols1-1">
|
<div class="cols cols1-1">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one. Selected palette choices should utilize the <code>selected</code> CSS class to visualize indicate that state.</p>
|
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one.</p>
|
||||||
<p>Note that while this example uses static markup for illustrative purposes, don't do this - use a front-end framework with repeaters to build the color choices.</p>
|
<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>
|
</div>
|
||||||
<mct-example><div style="height: 220px" title="Ignore me, I'm just here to provide space for this example.">
|
<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">
|
<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="l-click-area" ng-click="toggle.toggle()"></span>
|
||||||
<span class="color-swatch" style="background: rgb(255, 0, 0);"></span>
|
<span class="color-swatch" style="background: rgb(255, 0, 0);"></span>
|
||||||
<div class="menu l-palette l-color-palette" ng-show="toggle.isActive()">
|
<div class="menu l-color-palette" ng-show="toggle.isActive()">
|
||||||
<div class="l-palette-row l-option-row">
|
<div class="l-palette-row l-option-row">
|
||||||
<div class="l-palette-item s-palette-item no-selection"></div>
|
<div class="l-palette-item s-palette-item " ng-click="ngModel[field] = 'transparent'"></div>
|
||||||
<span class="l-palette-item-label">None</span>
|
<span class="l-palette-item-label">None</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="l-palette-row">
|
<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 class="l-palette-item s-palette-item" style="background: rgb(255, 255, 255);"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="l-palette-row">
|
<div class="l-palette-row">
|
||||||
<div class="l-palette-item s-palette-item selected" style="background: rgb(255, 0, 0);"></div>
|
<div class="l-palette-item s-palette-item" style="background: rgb(136, 32, 32);"></div>
|
||||||
<div class="l-palette-item s-palette-item" style="background: rgb(224, 64, 64);"></div>
|
<div class="l-palette-item s-palette-item" style="background: rgb(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(240, 160, 72);"></div>
|
||||||
<div class="l-palette-item s-palette-item" style="background: rgb(255, 248, 96);"></div>
|
<div class="l-palette-item s-palette-item" style="background: rgb(255, 248, 96);"></div>
|
||||||
|
@ -22,8 +22,6 @@
|
|||||||
|
|
||||||
/*global require,__dirname*/
|
/*global require,__dirname*/
|
||||||
|
|
||||||
require("v8-compile-cache");
|
|
||||||
|
|
||||||
var gulp = require('gulp'),
|
var gulp = require('gulp'),
|
||||||
sourcemaps = require('gulp-sourcemaps'),
|
sourcemaps = require('gulp-sourcemaps'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
|
11
index.html
11
index.html
@ -25,7 +25,8 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
<title></title>
|
<title></title>
|
||||||
<script src="bower_components/requirejs/require.js"> </script>
|
<script src="bower_components/requirejs/require.js">
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
var THIRTY_MINUTES = 30 * 60 * 1000;
|
var THIRTY_MINUTES = 30 * 60 * 1000;
|
||||||
|
|
||||||
@ -38,21 +39,18 @@
|
|||||||
);
|
);
|
||||||
openmct.install(openmct.plugins.MyItems());
|
openmct.install(openmct.plugins.MyItems());
|
||||||
openmct.install(openmct.plugins.LocalStorage());
|
openmct.install(openmct.plugins.LocalStorage());
|
||||||
openmct.install(openmct.plugins.Espresso());
|
openmct.install(openmct.plugins.Snow());
|
||||||
openmct.install(openmct.plugins.Generator());
|
openmct.install(openmct.plugins.Generator());
|
||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
openmct.install(openmct.plugins.ImportExport());
|
openmct.install(openmct.plugins.ImportExport());
|
||||||
openmct.install(openmct.plugins.AutoflowView({
|
|
||||||
type: "telemetry.panel"
|
|
||||||
}));
|
|
||||||
openmct.install(openmct.plugins.Conductor({
|
openmct.install(openmct.plugins.Conductor({
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
name: "Fixed",
|
name: "Fixed",
|
||||||
timeSystem: 'utc',
|
timeSystem: 'utc',
|
||||||
bounds: {
|
bounds: {
|
||||||
start: Date.now() - THIRTY_MINUTES,
|
start: Date.now() - 30 * 60 * 1000,
|
||||||
end: Date.now()
|
end: Date.now()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -67,7 +65,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}));
|
}));
|
||||||
openmct.install(openmct.plugins.SummaryWidget());
|
|
||||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||||
openmct.time.timeSystem('utc');
|
openmct.time.timeSystem('utc');
|
||||||
openmct.start();
|
openmct.start();
|
||||||
|
@ -36,7 +36,6 @@ module.exports = function(config) {
|
|||||||
files: [
|
files: [
|
||||||
{pattern: 'bower_components/**/*.js', included: false},
|
{pattern: 'bower_components/**/*.js', included: false},
|
||||||
{pattern: 'node_modules/d3-*/**/*.js', included: false},
|
{pattern: 'node_modules/d3-*/**/*.js', included: false},
|
||||||
{pattern: 'node_modules/vue/**/*.js', included: false},
|
|
||||||
{pattern: 'src/**/*.js', included: false},
|
{pattern: 'src/**/*.js', included: false},
|
||||||
{pattern: 'example/**/*.html', included: false},
|
{pattern: 'example/**/*.html', included: false},
|
||||||
{pattern: 'example/**/*.js', included: false},
|
{pattern: 'example/**/*.js', included: false},
|
||||||
@ -89,8 +88,7 @@ module.exports = function(config) {
|
|||||||
"dist/reports/coverage",
|
"dist/reports/coverage",
|
||||||
check: {
|
check: {
|
||||||
global: {
|
global: {
|
||||||
lines: 80,
|
lines: 80
|
||||||
excludes: ['src/plugins/plot/**/*.js']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -37,7 +37,6 @@ requirejs.config({
|
|||||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||||
"text": "bower_components/text/text",
|
"text": "bower_components/text/text",
|
||||||
"uuid": "bower_components/node-uuid/uuid",
|
"uuid": "bower_components/node-uuid/uuid",
|
||||||
"vue": "node_modules/vue/dist/vue.min",
|
|
||||||
"zepto": "bower_components/zepto/zepto.min",
|
"zepto": "bower_components/zepto/zepto.min",
|
||||||
"lodash": "bower_components/lodash/lodash",
|
"lodash": "bower_components/lodash/lodash",
|
||||||
"d3-selection": "node_modules/d3-selection/dist/d3-selection.min",
|
"d3-selection": "node_modules/d3-selection/dist/d3-selection.min",
|
||||||
@ -67,9 +66,6 @@ requirejs.config({
|
|||||||
"moment-duration-format": {
|
"moment-duration-format": {
|
||||||
"deps": ["moment"]
|
"deps": ["moment"]
|
||||||
},
|
},
|
||||||
"saveAs": {
|
|
||||||
"exports": "saveAs"
|
|
||||||
},
|
|
||||||
"screenfull": {
|
"screenfull": {
|
||||||
"exports": "screenfull"
|
"exports": "screenfull"
|
||||||
},
|
},
|
||||||
@ -101,7 +97,6 @@ define([
|
|||||||
var openmct = new MCT();
|
var openmct = new MCT();
|
||||||
|
|
||||||
openmct.legacyRegistry = defaultRegistry;
|
openmct.legacyRegistry = defaultRegistry;
|
||||||
openmct.install(openmct.plugins.Plot());
|
|
||||||
|
|
||||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||||
openmct.install(buildInfo(BUILD_CONSTANTS));
|
openmct.install(buildInfo(BUILD_CONSTANTS));
|
||||||
|
@ -15,8 +15,7 @@
|
|||||||
"d3-time-format": "^2.0.3",
|
"d3-time-format": "^2.0.3",
|
||||||
"express": "^4.13.1",
|
"express": "^4.13.1",
|
||||||
"minimist": "^1.1.1",
|
"minimist": "^1.1.1",
|
||||||
"request": "^2.69.0",
|
"request": "^2.69.0"
|
||||||
"vue": "^2.5.6"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bower": "^1.7.7",
|
"bower": "^1.7.7",
|
||||||
@ -50,8 +49,7 @@
|
|||||||
"moment": "^2.11.1",
|
"moment": "^2.11.1",
|
||||||
"node-bourbon": "^4.2.3",
|
"node-bourbon": "^4.2.3",
|
||||||
"requirejs": "2.1.x",
|
"requirejs": "2.1.x",
|
||||||
"split": "^1.0.0",
|
"split": "^1.0.0"
|
||||||
"v8-compile-cache": "^1.1.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node app.js",
|
"start": "node app.js",
|
||||||
@ -61,7 +59,7 @@
|
|||||||
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
"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'",
|
"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",
|
"docs": "npm run jsdoc ; npm run otherdoc",
|
||||||
"prepare": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
|
"prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -57,12 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<mct-representation key="representation.selected.key"
|
<mct-representation key="representation.selected.key"
|
||||||
mct-object="representation.selected.key && domainObject"
|
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>
|
</mct-representation>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,21 +19,12 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div ng-controller="InspectorController as controller">
|
<div ng-controller="InspectorController">
|
||||||
|
<div ng-repeat="region in regions">
|
||||||
<mct-representation
|
<mct-representation
|
||||||
key="'object-properties'"
|
key="region.content.key"
|
||||||
mct-object="controller.selectedItem()"
|
mct-object="domainObject"
|
||||||
ng-model="ngModel">
|
ng-model="ngModel">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
|
|
||||||
<div ng-if="!controller.hasProviderView()">
|
|
||||||
<mct-representation
|
|
||||||
key="inspectorKey"
|
|
||||||
mct-object="controller.selectedItem()"
|
|
||||||
ng-model="ngModel">
|
|
||||||
</mct-representation>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='inspector-provider-view'>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,6 +38,8 @@
|
|||||||
ng-class="{ last:($index + 1) === contextualParents.length }">
|
ng-class="{ last:($index + 1) === contextualParents.length }">
|
||||||
<mct-representation key="'label'"
|
<mct-representation key="'label'"
|
||||||
mct-object="parent"
|
mct-object="parent"
|
||||||
|
ng-model="ngModel"
|
||||||
|
ng-click="ngModel.selectedObject = parent"
|
||||||
class="location-item">
|
class="location-item">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
</span>
|
</span>
|
||||||
@ -49,6 +51,8 @@
|
|||||||
ng-class="{ last:($index + 1) === primaryParents.length }">
|
ng-class="{ last:($index + 1) === primaryParents.length }">
|
||||||
<mct-representation key="'label'"
|
<mct-representation key="'label'"
|
||||||
mct-object="parent"
|
mct-object="parent"
|
||||||
|
ng-model="ngModel"
|
||||||
|
ng-click="ngModel.selectedObject = parent"
|
||||||
class="location-item">
|
class="location-item">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
</span>
|
</span>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class="abs top-bar">
|
<div class="abs top-bar">
|
||||||
<div class="dialog-title">{{ngModel.title}}</div>
|
<div class="title">{{ngModel.title}}</div>
|
||||||
<div class="hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
|
<div class="hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='abs editor'>
|
<div class='abs editor'>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<div class="l-message"
|
<div class="l-message"
|
||||||
ng-class="'message-severity-' + ngModel.severity">
|
ng-class="'message-severity-' + ngModel.severity">
|
||||||
<div class="w-message-contents">
|
<div class="ui-symbol type-icon message-type"></div>
|
||||||
|
<div class="message-contents">
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<div class="title">{{ngModel.title}}</div>
|
<div class="title">{{ngModel.title}}</div>
|
||||||
</div>
|
|
||||||
<div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
|
<div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
|
||||||
|
</div>
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
<div class="message-action">
|
<div class="message-action">
|
||||||
{{ngModel.actionText}}
|
{{ngModel.actionText}}
|
||||||
@ -24,6 +25,8 @@
|
|||||||
ng-click="ngModel.primaryOption.callback()">
|
ng-click="ngModel.primaryOption.callback()">
|
||||||
{{ngModel.primaryOption.label}}
|
{{ngModel.primaryOption.label}}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
<mct-container key="overlay">
|
<mct-container key="overlay" class="t-message-list">
|
||||||
<div class="t-message-list">
|
<div class="message-contents">
|
||||||
<div class="top-bar">
|
<div class="abs top-bar">
|
||||||
<div class="dialog-title">{{ngModel.dialog.title}}</div>
|
<div class="title">{{ngModel.dialog.title}}</div>
|
||||||
<div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1 ||
|
<div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1 ||
|
||||||
ngModel.dialog.messages.length == 0">s</span>
|
ngModel.dialog.messages.length == 0">s</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-messages">
|
<div class="abs message-body">
|
||||||
<mct-include
|
<mct-include
|
||||||
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
|
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
|
||||||
key="'message'" ng-model="msg.model"></mct-include>
|
key="'message'" ng-model="msg.model"></mct-include>
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom-bar">
|
<div class="abs bottom-bar">
|
||||||
<a ng-repeat="dialogAction in ngModel.dialog.actions"
|
<a ng-repeat="dialogAction in ngModel.dialog.actions"
|
||||||
class="s-button major"
|
class="s-button major"
|
||||||
ng-click="dialogAction.action()">
|
ng-click="dialogAction.action()">
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
-->
|
-->
|
||||||
<mct-container key="overlay">
|
<mct-container key="overlay">
|
||||||
<div class="abs top-bar">
|
<div class="abs top-bar">
|
||||||
<div class="dialog-title">{{ngModel.dialog.title}}</div>
|
<div class="title">{{ngModel.dialog.title}}</div>
|
||||||
<div class="hint">{{ngModel.dialog.hint}}</div>
|
<div class="hint">{{ngModel.dialog.hint}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='abs editor'>
|
<div class='abs editor'>
|
||||||
|
@ -121,8 +121,7 @@ define([
|
|||||||
"key": "ElementsController",
|
"key": "ElementsController",
|
||||||
"implementation": ElementsController,
|
"implementation": ElementsController,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$scope",
|
"$scope"
|
||||||
"openmct"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -300,6 +299,9 @@ define([
|
|||||||
{
|
{
|
||||||
"key": "edit-elements",
|
"key": "edit-elements",
|
||||||
"template": elementsTemplate,
|
"template": elementsTemplate,
|
||||||
|
"uses": [
|
||||||
|
"composition"
|
||||||
|
],
|
||||||
"gestures": [
|
"gestures": [
|
||||||
"drop"
|
"drop"
|
||||||
]
|
]
|
||||||
@ -383,10 +385,7 @@ define([
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"implementation": EditToolbarRepresenter,
|
"implementation": EditToolbarRepresenter
|
||||||
"depends": [
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"constants": [
|
"constants": [
|
||||||
|
@ -61,12 +61,7 @@
|
|||||||
<mct-representation key="representation.selected.key"
|
<mct-representation key="representation.selected.key"
|
||||||
mct-object="representation.selected.key && domainObject"
|
mct-object="representation.selected.key && domainObject"
|
||||||
class="abs flex-elem grows object-holder-main scroll"
|
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>
|
</mct-representation>
|
||||||
</div><!--/ l-object-wrapper-inner -->
|
</div><!--/ l-object-wrapper-inner -->
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
ng-model="filterBy">
|
ng-model="filterBy">
|
||||||
</mct-include>
|
</mct-include>
|
||||||
<div class="flex-elem grows vscroll">
|
<div class="flex-elem grows vscroll">
|
||||||
<ul class="tree" ng-if="composition.length > 0">
|
<ul class="tree">
|
||||||
<li ng-repeat="containedObject in composition | filter:searchElements">
|
<li ng-repeat="containedObject in composition | filter:searchElements">
|
||||||
<span class="tree-item">
|
<span class="tree-item">
|
||||||
<mct-representation
|
<mct-representation
|
||||||
@ -36,6 +36,5 @@
|
|||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div ng-if="composition.length === 0">No contained elements</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -101,15 +101,10 @@ define(
|
|||||||
*/
|
*/
|
||||||
EditorCapability.prototype.finish = function () {
|
EditorCapability.prototype.finish = function () {
|
||||||
var domainObject = this.domainObject;
|
var domainObject = this.domainObject;
|
||||||
|
|
||||||
if (this.transactionService.isActive()) {
|
|
||||||
return this.transactionService.cancel().then(function () {
|
return this.transactionService.cancel().then(function () {
|
||||||
domainObject.getCapability("status").set("editing", false);
|
domainObject.getCapability("status").set("editing", false);
|
||||||
return domainObject;
|
return domainObject;
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
return Promise.resolve(domainObject);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,6 +28,16 @@ define(
|
|||||||
[],
|
[],
|
||||||
function () {
|
function () {
|
||||||
|
|
||||||
|
function isDirty(domainObject) {
|
||||||
|
var navigatedObject = domainObject,
|
||||||
|
editorCapability = navigatedObject &&
|
||||||
|
navigatedObject.getCapability("editor");
|
||||||
|
|
||||||
|
return editorCapability &&
|
||||||
|
editorCapability.isEditContextRoot() &&
|
||||||
|
editorCapability.dirty();
|
||||||
|
}
|
||||||
|
|
||||||
function cancelEditing(domainObject) {
|
function cancelEditing(domainObject) {
|
||||||
var navigatedObject = domainObject,
|
var navigatedObject = domainObject,
|
||||||
editorCapability = navigatedObject &&
|
editorCapability = navigatedObject &&
|
||||||
@ -49,7 +59,10 @@ define(
|
|||||||
|
|
||||||
var removeCheck = navigationService
|
var removeCheck = navigationService
|
||||||
.checkBeforeNavigation(function () {
|
.checkBeforeNavigation(function () {
|
||||||
|
if (isDirty(domainObject)) {
|
||||||
return "Continuing will cause the loss of any unsaved changes.";
|
return "Continuing will cause the loss of any unsaved changes.";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.$on('$destroy', function () {
|
$scope.$on('$destroy', function () {
|
||||||
|
@ -29,11 +29,7 @@ define(
|
|||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function ElementsController($scope, openmct) {
|
function ElementsController($scope) {
|
||||||
this.scope = $scope;
|
|
||||||
this.scope.composition = [];
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
function filterBy(text) {
|
function filterBy(text) {
|
||||||
if (typeof text === 'undefined') {
|
if (typeof text === 'undefined') {
|
||||||
return $scope.searchText;
|
return $scope.searchText;
|
||||||
@ -51,54 +47,10 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSelection(selection) {
|
|
||||||
if (!selection[0]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.mutationListener) {
|
|
||||||
self.mutationListener();
|
|
||||||
delete self.mutationListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
var domainObject = selection[0].context.oldItem;
|
|
||||||
self.refreshComposition(domainObject);
|
|
||||||
|
|
||||||
if (domainObject) {
|
|
||||||
self.mutationListener = domainObject.getCapability('mutation')
|
|
||||||
.listen(self.refreshComposition.bind(self, domainObject));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.filterBy = filterBy;
|
$scope.filterBy = filterBy;
|
||||||
$scope.searchElements = searchElements;
|
$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;
|
return ElementsController;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -38,7 +38,7 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @implements {Representer}
|
* @implements {Representer}
|
||||||
*/
|
*/
|
||||||
function EditToolbarRepresenter(openmct, scope, element, attrs) {
|
function EditToolbarRepresenter(scope, element, attrs) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Mark changes as ready to persist
|
// Mark changes as ready to persist
|
||||||
@ -109,7 +109,6 @@ define(
|
|||||||
this.updateSelection = updateSelection;
|
this.updateSelection = updateSelection;
|
||||||
this.toolbar = undefined;
|
this.toolbar = undefined;
|
||||||
this.toolbarObject = {};
|
this.toolbarObject = {};
|
||||||
this.openmct = openmct;
|
|
||||||
|
|
||||||
// If this representation exposes a toolbar, set up watches
|
// If this representation exposes a toolbar, set up watches
|
||||||
// to synchronize with it.
|
// to synchronize with it.
|
||||||
@ -147,7 +146,7 @@ define(
|
|||||||
// Expose the toolbar object to the parent scope
|
// Expose the toolbar object to the parent scope
|
||||||
initialize(definition);
|
initialize(definition);
|
||||||
// Create a selection scope
|
// Create a selection scope
|
||||||
this.setSelection(new EditToolbarSelection(this.openmct));
|
this.setSelection(new EditToolbarSelection());
|
||||||
// Initialize toolbar to an empty selection
|
// Initialize toolbar to an empty selection
|
||||||
this.updateSelection([]);
|
this.updateSelection([]);
|
||||||
};
|
};
|
||||||
|
@ -38,24 +38,10 @@ define(
|
|||||||
* @memberof platform/commonUI/edit
|
* @memberof platform/commonUI/edit
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function EditToolbarSelection(openmct) {
|
function EditToolbarSelection() {
|
||||||
this.selection = [{}];
|
this.selection = [{}];
|
||||||
this.selecting = false;
|
this.selecting = false;
|
||||||
this.selectedObj = undefined;
|
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,7 +62,6 @@ define(
|
|||||||
);
|
);
|
||||||
mockTransactionService.commit.andReturn(fastPromise());
|
mockTransactionService.commit.andReturn(fastPromise());
|
||||||
mockTransactionService.cancel.andReturn(fastPromise());
|
mockTransactionService.cancel.andReturn(fastPromise());
|
||||||
mockTransactionService.isActive = jasmine.createSpy('isActive');
|
|
||||||
|
|
||||||
mockStatusCapability = jasmine.createSpyObj(
|
mockStatusCapability = jasmine.createSpyObj(
|
||||||
"statusCapability",
|
"statusCapability",
|
||||||
@ -142,7 +141,6 @@ define(
|
|||||||
|
|
||||||
describe("finish", function () {
|
describe("finish", function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockTransactionService.isActive.andReturn(true);
|
|
||||||
capability.edit();
|
capability.edit();
|
||||||
capability.finish();
|
capability.finish();
|
||||||
});
|
});
|
||||||
@ -154,23 +152,6 @@ define(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("finish", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
mockTransactionService.isActive.andReturn(false);
|
|
||||||
capability.edit();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not cancel transaction when transaction is not active", function () {
|
|
||||||
capability.finish();
|
|
||||||
expect(mockTransactionService.cancel).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a promise", function () {
|
|
||||||
expect(capability.finish() instanceof Promise).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("dirty", function () {
|
describe("dirty", function () {
|
||||||
var model = {};
|
var model = {};
|
||||||
|
|
||||||
|
@ -104,10 +104,10 @@ define(
|
|||||||
mockEditorCapability.isEditContextRoot.andReturn(false);
|
mockEditorCapability.isEditContextRoot.andReturn(false);
|
||||||
mockEditorCapability.dirty.andReturn(false);
|
mockEditorCapability.dirty.andReturn(false);
|
||||||
|
|
||||||
expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
|
expect(checkFn()).toBe(false);
|
||||||
|
|
||||||
mockEditorCapability.isEditContextRoot.andReturn(true);
|
mockEditorCapability.isEditContextRoot.andReturn(true);
|
||||||
expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
|
expect(checkFn()).toBe(false);
|
||||||
|
|
||||||
mockEditorCapability.dirty.andReturn(true);
|
mockEditorCapability.dirty.andReturn(true);
|
||||||
expect(checkFn())
|
expect(checkFn())
|
||||||
|
@ -27,47 +27,11 @@ define(
|
|||||||
|
|
||||||
describe("The Elements Pane controller", function () {
|
describe("The Elements Pane controller", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
mockOpenMCT,
|
|
||||||
mockSelection,
|
|
||||||
mockDomainObject,
|
|
||||||
mockMutationCapability,
|
|
||||||
mockUnlisten,
|
|
||||||
selectable = [],
|
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockUnlisten = jasmine.createSpy('unlisten');
|
mockScope = jasmine.createSpy("$scope");
|
||||||
mockMutationCapability = jasmine.createSpyObj("mutationCapability", [
|
controller = new ElementsController(mockScope);
|
||||||
"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) {
|
function getModel(model) {
|
||||||
@ -99,44 +63,6 @@ define(
|
|||||||
expect(objects.filter(mockScope.searchElements).length).toBe(4);
|
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,9 +29,7 @@ define(
|
|||||||
mockElement,
|
mockElement,
|
||||||
testAttrs,
|
testAttrs,
|
||||||
mockUnwatch,
|
mockUnwatch,
|
||||||
representer,
|
representer;
|
||||||
mockOpenMCT,
|
|
||||||
mockSelection;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockScope = jasmine.createSpyObj(
|
mockScope = jasmine.createSpyObj(
|
||||||
@ -48,18 +46,7 @@ define(
|
|||||||
|
|
||||||
mockScope.$parent.$watchCollection.andReturn(mockUnwatch);
|
mockScope.$parent.$watchCollection.andReturn(mockUnwatch);
|
||||||
|
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.andReturn([]);
|
|
||||||
mockOpenMCT = {
|
|
||||||
selection: mockSelection
|
|
||||||
};
|
|
||||||
|
|
||||||
representer = new EditToolbarRepresenter(
|
representer = new EditToolbarRepresenter(
|
||||||
mockOpenMCT,
|
|
||||||
mockScope,
|
mockScope,
|
||||||
mockElement,
|
mockElement,
|
||||||
testAttrs
|
testAttrs
|
||||||
|
@ -28,25 +28,13 @@ define(
|
|||||||
var testProxy,
|
var testProxy,
|
||||||
testElement,
|
testElement,
|
||||||
otherElement,
|
otherElement,
|
||||||
selection,
|
selection;
|
||||||
mockSelection,
|
|
||||||
mockOpenMCT;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testProxy = { someKey: "some value" };
|
testProxy = { someKey: "some value" };
|
||||||
testElement = { someOtherKey: "some other value" };
|
testElement = { someOtherKey: "some other value" };
|
||||||
otherElement = { yetAnotherKey: 42 };
|
otherElement = { yetAnotherKey: 42 };
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
selection = new EditToolbarSelection();
|
||||||
// 'select',
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.andReturn([]);
|
|
||||||
mockOpenMCT = {
|
|
||||||
selection: mockSelection
|
|
||||||
};
|
|
||||||
selection = new EditToolbarSelection(mockOpenMCT);
|
|
||||||
selection.proxy(testProxy);
|
selection.proxy(testProxy);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -120,10 +120,17 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If dates come in this format, parse them with built in parser to avoid
|
||||||
|
// 30x longer parsing costs via momentjs.
|
||||||
|
var iso9601format = /^\d{4}-\d{2}-\d{2}[T\ ]\d{2}:\d{2}:\d{2}.\d{3}Z?/;
|
||||||
|
|
||||||
UTCTimeFormat.prototype.parse = function (text) {
|
UTCTimeFormat.prototype.parse = function (text) {
|
||||||
if (typeof text === 'number') {
|
if (typeof text === 'number') {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
if (iso9601format.test(text)) {
|
||||||
|
return moment.utc(new Date(text)).valueOf();
|
||||||
|
}
|
||||||
return moment.utc(text, DATE_FORMATS).valueOf();
|
return moment.utc(text, DATE_FORMATS).valueOf();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,7 +41,6 @@ define([
|
|||||||
"./src/controllers/BannerController",
|
"./src/controllers/BannerController",
|
||||||
"./src/directives/MCTContainer",
|
"./src/directives/MCTContainer",
|
||||||
"./src/directives/MCTDrag",
|
"./src/directives/MCTDrag",
|
||||||
"./src/directives/MCTSelectable",
|
|
||||||
"./src/directives/MCTClickElsewhere",
|
"./src/directives/MCTClickElsewhere",
|
||||||
"./src/directives/MCTResize",
|
"./src/directives/MCTResize",
|
||||||
"./src/directives/MCTPopup",
|
"./src/directives/MCTPopup",
|
||||||
@ -91,7 +90,6 @@ define([
|
|||||||
BannerController,
|
BannerController,
|
||||||
MCTContainer,
|
MCTContainer,
|
||||||
MCTDrag,
|
MCTDrag,
|
||||||
MCTSelectable,
|
|
||||||
MCTClickElsewhere,
|
MCTClickElsewhere,
|
||||||
MCTResize,
|
MCTResize,
|
||||||
MCTPopup,
|
MCTPopup,
|
||||||
@ -330,13 +328,6 @@ define([
|
|||||||
"$document"
|
"$document"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "mctSelectable",
|
|
||||||
"implementation": MCTSelectable,
|
|
||||||
"depends": [
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "mctClickElsewhere",
|
"key": "mctClickElsewhere",
|
||||||
"implementation": MCTClickElsewhere,
|
"implementation": MCTClickElsewhere,
|
||||||
|
@ -137,11 +137,6 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
&.holder:not(:last-child) { margin-bottom: $interiorMarginLg; }
|
&.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); }
|
.flex-container { @include flex-direction(column); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.l-fixed-position-item {
|
.l-fixed-position-item {
|
||||||
border-width: 1px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
&.s-not-selected {
|
&.s-not-selected {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
@ -180,20 +180,6 @@ a.disabled {
|
|||||||
@include ellipsize();
|
@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,
|
.scrolling,
|
||||||
.scroll {
|
.scroll {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
/********************************* CONTROLS */
|
/********************************* CONTROLS */
|
||||||
@import "controls/breadcrumb";
|
@import "controls/breadcrumb";
|
||||||
@import "controls/buttons";
|
@import "controls/buttons";
|
||||||
@import "controls/palette";
|
@import "controls/color-palette";
|
||||||
@import "controls/controls";
|
@import "controls/controls";
|
||||||
@import "controls/lists";
|
@import "controls/lists";
|
||||||
@import "controls/menus";
|
@import "controls/menus";
|
||||||
@ -81,4 +81,3 @@
|
|||||||
@import "autoflow";
|
@import "autoflow";
|
||||||
@import "features/imagery";
|
@import "features/imagery";
|
||||||
@import "features/time-display";
|
@import "features/time-display";
|
||||||
@import "widgets";
|
|
||||||
|
@ -1,306 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/************************************************************* WIDGET OBJECT */
|
|
||||||
.l-summary-widget {
|
|
||||||
// Widget layout classes here
|
|
||||||
@include ellipsize();
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
.widget-label:before {
|
|
||||||
// Widget icon
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin-right: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.s-summary-widget {
|
|
||||||
// Widget style classes here
|
|
||||||
@include boxShdw($shdwBtns);
|
|
||||||
border-radius: $basicCr;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
cursor: default;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
padding: $interiorMarginLg $interiorMarginLg * 2;
|
|
||||||
&[href] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-edit-holder {
|
|
||||||
// Hide edit area when in browse mode
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-rule-header {
|
|
||||||
@extend .l-flex-row;
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,67 +19,67 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
.l-palette {
|
|
||||||
box-sizing: border-box;
|
.s-palette-item {
|
||||||
padding: $interiorMargin !important;
|
$colorSelectedColor: #fff;
|
||||||
|
@include txtShdwSubtle(0.8);
|
||||||
|
@include trans-prop-nice-fade(0.25s);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
&:hover {
|
||||||
|
@include trans-prop-nice-fade(0);
|
||||||
|
border-color: $colorSelectedColor !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-palette-row {
|
.l-color-palette {
|
||||||
$d: 16px;
|
$d: 16px;
|
||||||
$m: 1;
|
|
||||||
$colorsPerRow: 10;
|
$colorsPerRow: 10;
|
||||||
display: flex;
|
$m: 1;
|
||||||
flex-wrap: wrap;
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: $interiorMargin !important;
|
||||||
|
|
||||||
|
.l-palette-row {
|
||||||
|
@include clearfix;
|
||||||
line-height: $d;
|
line-height: $d;
|
||||||
width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
|
width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
|
||||||
|
|
||||||
&.l-option-row {
|
|
||||||
margin-bottom: $interiorMargin;
|
|
||||||
.s-palette-item {
|
|
||||||
border-color: $colorPaletteFg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-palette-item {
|
.l-palette-item {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: block;
|
display: block;
|
||||||
|
float: left;
|
||||||
height: $d; width: $d;
|
height: $d; width: $d;
|
||||||
min-width: $d;
|
|
||||||
line-height: $d * 0.9;
|
line-height: $d * 0.9;
|
||||||
margin: 0 ($m * 1px) ($m * 1px) 0;
|
margin: 0 ($m * 1px) ($m * 1px) 0;
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
&:before {
|
||||||
|
// Check mark for selected items
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.s-palette-item {
|
.l-palette-item-label {
|
||||||
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;
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.l-option-row {
|
||||||
|
margin-bottom: $interiorMargin;
|
||||||
|
.s-palette-item {
|
||||||
|
border-color: $colorBodyFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-inline-palette {
|
.l-inline-color-palette {
|
||||||
|
// Is an <li>
|
||||||
.l-palette-row {
|
.l-palette-row {
|
||||||
|
@include display(flex);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.l-palette-item {
|
.l-palette-item {
|
||||||
//@include display(flex);
|
@include display(flex);
|
||||||
@include flex(1 0 auto);
|
@include flex(1 0 auto);
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
min-width: auto;
|
|
||||||
width: auto;
|
|
||||||
&:before {
|
&:before {
|
||||||
content: '';
|
content: '';
|
||||||
padding-top: 75%;
|
padding-top: 75%;
|
@ -281,7 +281,7 @@ input[type="number"] {
|
|||||||
input[type="text"].lg { width: 100% !important; }
|
input[type="text"].lg { width: 100% !important; }
|
||||||
.l-input-med input[type="text"],
|
.l-input-med input[type="text"],
|
||||||
input[type="text"].med { width: 200px !important; }
|
input[type="text"].med { width: 200px !important; }
|
||||||
input[type="text"].sm, input[type="number"].sm { width: 50px !important; }
|
input[type="text"].sm { width: 50px !important; }
|
||||||
.l-numeric input[type="text"],
|
.l-numeric input[type="text"],
|
||||||
input[type="text"].numeric { text-align: right; }
|
input[type="text"].numeric { text-align: right; }
|
||||||
|
|
||||||
@ -337,10 +337,14 @@ input[type="text"].s-input-inline,
|
|||||||
.select {
|
.select {
|
||||||
@include btnSubtle($bg: $colorSelectBg);
|
@include btnSubtle($bg: $colorSelectBg);
|
||||||
@extend .icon-arrow-down; // Context arrow
|
@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;
|
display: inline-block;
|
||||||
padding: 0 $interiorMargin;
|
padding: 0 $interiorMargin;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
line-height: $formInputH;
|
||||||
select {
|
select {
|
||||||
@include appearance(none);
|
@include appearance(none);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -356,13 +360,11 @@ input[type="text"].s-input-inline,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:before {
|
&:before {
|
||||||
@include transform(translateY(-50%));
|
pointer-events: none;
|
||||||
color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
|
color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
|
||||||
display: block;
|
display: block;
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: $interiorMargin;
|
right: $interiorMargin; top: 0;
|
||||||
top: 50%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +416,8 @@ input[type="text"].s-input-inline,
|
|||||||
.l-elem-wrapper {
|
.l-elem-wrapper {
|
||||||
mct-representation {
|
mct-representation {
|
||||||
// Holds the context-available item
|
// 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;
|
min-width: 0.7em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -580,6 +583,7 @@ input[type="text"].s-input-inline,
|
|||||||
height: $h;
|
height: $h;
|
||||||
margin-top: 1 + floor($h/2) * -1;
|
margin-top: 1 + floor($h/2) * -1;
|
||||||
@include btnSubtle(pullForward($colorBtnBg, 10%));
|
@include btnSubtle(pullForward($colorBtnBg, 10%));
|
||||||
|
//border-radius: 50% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin sliderKnobRound() {
|
@mixin sliderKnobRound() {
|
||||||
@ -594,6 +598,7 @@ input[type="text"].s-input-inline,
|
|||||||
|
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
// HTML5 range inputs
|
// HTML5 range inputs
|
||||||
|
|
||||||
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
|
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
|
||||||
background: transparent; /* Otherwise white in Chrome */
|
background: transparent; /* Otherwise white in Chrome */
|
||||||
&:focus {
|
&:focus {
|
||||||
@ -751,30 +756,6 @@ textarea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-switcher,
|
|
||||||
.t-btn-view-large {
|
|
||||||
@include trans-prop-nice-fade($controlFadeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-control {
|
|
||||||
@extend .icon-arrow-right;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.75em;
|
|
||||||
&:before {
|
|
||||||
position: absolute;
|
|
||||||
@include trans-prop-nice(transform, 100ms);
|
|
||||||
@include transform-origin(center);
|
|
||||||
}
|
|
||||||
&.expanded:before {
|
|
||||||
@include transform(rotate(90deg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.grippy {
|
|
||||||
@extend .icon-grippy;
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************** BROWSER ELEMENTS */
|
/******************************************************** BROWSER ELEMENTS */
|
||||||
body.desktop {
|
body.desktop {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
@ -29,27 +29,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
font-size: 16px;
|
font-size: 16px; //120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-label {
|
.title-label {
|
||||||
margin-left: $interiorMarginSm;
|
margin-left: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-swatch,
|
|
||||||
.color-swatch {
|
.color-swatch {
|
||||||
// Used in color menu buttons in toolbar
|
// Used in color menu buttons in toolbar
|
||||||
$d: 10px;
|
$d: 10px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px solid rgba($colorBtnFg, 0.2);
|
border: 1px solid rgba($colorBtnFg, 0.2);
|
||||||
height: $d; width: $d;
|
height: $d;
|
||||||
line-height: $d;
|
width: $d;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-left: $interiorMarginSm;
|
margin-left: $interiorMarginSm;
|
||||||
margin-top: -2px;
|
margin-top: -2px;
|
||||||
&:not(.no-selection) {
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:after {
|
&:after {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
/******************************************************************* STATUS BLOCK ELEMS */
|
|
||||||
@mixin statusBannerColors($bg, $fg: $colorStatusFg) {
|
@mixin statusBannerColors($bg, $fg: $colorStatusFg) {
|
||||||
$bgPb: 30%;
|
$bgPb: 30%;
|
||||||
$bgPbD: 10%;
|
$bgPbD: 10%;
|
||||||
@ -140,7 +140,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************* MESSAGE BANNERS */
|
/* Styles for messages and message banners */
|
||||||
.message {
|
.message {
|
||||||
&.block {
|
&.block {
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
@ -196,6 +196,7 @@
|
|||||||
padding: 0 $interiorMargin;
|
padding: 0 $interiorMargin;
|
||||||
}
|
}
|
||||||
.close {
|
.close {
|
||||||
|
//@include test(red, 0.7);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 7px;
|
font-size: 7px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
@ -239,147 +240,132 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************* MESSAGES */
|
@mixin messageBlock($iconW: 32px) {
|
||||||
|
.type-icon.message-type {
|
||||||
/* 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);
|
@include txtShdw($shdwStatusIc);
|
||||||
@extend .icon-bell;
|
@extend .icon-bell;
|
||||||
color: $colorStatusDefault;
|
color: $colorStatusDefault;
|
||||||
font-size: $iconW;
|
font-size: $iconW;
|
||||||
|
padding: 1px;
|
||||||
width: $iconW + 2;
|
width: $iconW + 2;
|
||||||
margin-right: $interiorMarginLg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.message-severity-info:before {
|
.message-severity-info .type-icon.message-type {
|
||||||
@extend .icon-info;
|
@extend .icon-info;
|
||||||
color: $colorInfo;
|
color: $colorInfo;
|
||||||
}
|
}
|
||||||
|
.message-severity-alert .type-icon.message-type {
|
||||||
&.message-severity-alert:before {
|
@extend .icon-bell;
|
||||||
color: $colorWarningLo;
|
color: $colorWarningLo;
|
||||||
}
|
}
|
||||||
|
.message-severity-error .type-icon.message-type {
|
||||||
&.message-severity-error:before {
|
|
||||||
@extend .icon-alert-rect;
|
@extend .icon-alert-rect;
|
||||||
color: $colorWarningHi;
|
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 {
|
||||||
.w-message-contents {
|
@include display(flex);
|
||||||
|
@include flex-direction(row);
|
||||||
|
@include align-items(stretch);
|
||||||
|
.type-icon.message-type {
|
||||||
|
@include flex(0 1 auto);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.message-contents {
|
||||||
@include flex(1 1 auto);
|
@include flex(1 1 auto);
|
||||||
@include display(flex);
|
margin-left: $overlayMargin;
|
||||||
@include flex-direction(column);
|
position: relative;
|
||||||
|
|
||||||
> div,
|
|
||||||
> span {
|
|
||||||
//@include test(red);
|
|
||||||
margin-bottom: $interiorMargin;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
.top-bar,
|
||||||
.message-body {
|
.message-body {
|
||||||
@include flex(1 1 100%);
|
margin-bottom: $interiorMarginLg * 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton in an overlay dialog
|
|
||||||
.t-message-single .l-message,
|
// Message as singleton
|
||||||
.t-message-single.l-message {
|
.t-message-single {
|
||||||
$iconW: 80px;
|
@include messageBlock(80px);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.desktop .t-message-single {
|
||||||
|
.l-message,
|
||||||
|
.bottom-bar {
|
||||||
@include absPosDefault();
|
@include absPosDefault();
|
||||||
padding: 0;
|
|
||||||
&:before {
|
|
||||||
font-size: $iconW;
|
|
||||||
width: $iconW + 2;
|
|
||||||
}
|
}
|
||||||
.title {
|
|
||||||
font-size: 1.2em;
|
.bottom-bar {
|
||||||
|
top: auto;
|
||||||
|
height: $ovrFooterH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton inline in a view
|
@include phonePortrait {
|
||||||
.t-message-inline .l-message,
|
.t-message-single {
|
||||||
.t-message-inline.l-message {
|
.l-message {
|
||||||
border-radius: $controlCr;
|
@include flex-direction(column);
|
||||||
&.message-severity-info { background-color: rgba($colorInfo, 0.3); }
|
.message-contents { margin-left: 0; }
|
||||||
&.message-severity-alert { background-color: rgba($colorWarningLo, 0.3); }
|
}
|
||||||
&.message-severity-error { background-color: rgba($colorWarningHi, 0.3); }
|
.type-icon.message-type {
|
||||||
|
margin-bottom: $interiorMarginLg;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.w-message-contents.l-message-body-only {
|
.bottom-bar {
|
||||||
.message-body {
|
text-align: center !important;
|
||||||
margin-top: $interiorMargin;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In a list
|
// Messages in list
|
||||||
.t-message-list {
|
.t-message-list {
|
||||||
@include absPosDefault();
|
@include messageBlock(32px);
|
||||||
@include display(flex);
|
|
||||||
@include flex-direction(column);
|
|
||||||
|
|
||||||
> div,
|
.message-contents {
|
||||||
> span {
|
|
||||||
margin-bottom: $interiorMargin;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-messages {
|
|
||||||
@include flex(1 1 100%);
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-right: $interiorMargin;
|
|
||||||
}
|
|
||||||
// Each message
|
|
||||||
.l-message {
|
.l-message {
|
||||||
border-radius: $controlCr;
|
border-radius: $controlCr;
|
||||||
background: rgba($colorOvrFg, 0.1);
|
background: rgba($colorOvrFg, 0.1);
|
||||||
margin-bottom: $interiorMargin;
|
margin-bottom: $interiorMargin;
|
||||||
.hint,
|
padding: $interiorMarginLg;
|
||||||
|
|
||||||
|
.message-contents,
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
text-align: left;
|
position: relative;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
.message-contents {
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-left: $interiorMarginLg;
|
||||||
|
.message-action { color: pushBack($colorOvrFg, 20%); }
|
||||||
|
.bottom-bar { text-align: left; }
|
||||||
|
}
|
||||||
|
|
||||||
@include phonePortrait {
|
.top-bar,
|
||||||
.t-message-single .l-message,
|
.message-body {
|
||||||
.t-message-single.l-message {
|
|
||||||
@include flex-direction(column);
|
|
||||||
&:before {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-bottom: $interiorMarginLg;
|
margin-bottom: $interiorMarginLg;
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-bar {
|
|
||||||
text-align: center;
|
|
||||||
.s-button {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.desktop .t-message-list {
|
body.desktop .t-message-list {
|
||||||
.w-message-contents { padding-right: $interiorMargin; }
|
.message-contents .l-message { margin-right: $interiorMarginLg; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert elements in views
|
// Alert elements in views
|
||||||
|
@ -80,32 +80,23 @@
|
|||||||
|
|
||||||
// Editing Grids
|
// Editing Grids
|
||||||
.l-grid-holder {
|
.l-grid-holder {
|
||||||
|
display: block;
|
||||||
.l-grid {
|
.l-grid {
|
||||||
&.l-grid-x { @include bgTicks($colorGridLines, 'x'); }
|
&.l-grid-x { @include bgTicks($colorGridLines, 'x'); }
|
||||||
&.l-grid-y { @include bgTicks($colorGridLines, 'y'); }
|
&.l-grid-y { @include bgTicks($colorGridLines, 'y'); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display grid when selected or selection parent.
|
// Prevent nested frames from showing their grids
|
||||||
.s-selected .l-grid-holder,
|
.t-frame-outer .l-grid-holder { display: none !important; }
|
||||||
.s-selected-parent .l-grid-holder {
|
|
||||||
display: block;
|
// Prevent nested elements from showing s-hover-border
|
||||||
|
.t-frame-outer .s-hover-border {
|
||||||
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display in nested frames...
|
// Prevent nested frames from being selectable until we have proper sub-object editing
|
||||||
.t-frame-outer {
|
.t-frame-outer .t-frame-outer {
|
||||||
// ...when drilled in or selection parent...
|
pointer-events: none;
|
||||||
&.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,19 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
.section-header {
|
.section-header {
|
||||||
border-radius: $basicCr;
|
|
||||||
background: $colorFormSectionHeader;
|
|
||||||
color: lighten($colorBodyFg, 20%);
|
|
||||||
font-size: inherit;
|
|
||||||
margin: $interiorMargin 0;
|
|
||||||
padding: $formTBPad $formLRPad;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
.view-control {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: $interiorMargin;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
.form {
|
||||||
@ -53,6 +41,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
border-radius: $basicCr;
|
||||||
|
background: $colorFormSectionHeader;
|
||||||
|
$c: lighten($colorBodyFg, 20%);
|
||||||
|
color: $c;
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding: $formTBPad $formLRPad;
|
||||||
|
}
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
$m: $interiorMargin;
|
$m: $interiorMargin;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -60,6 +57,9 @@
|
|||||||
margin-bottom: $interiorMarginLg * 2;
|
margin-bottom: $interiorMarginLg * 2;
|
||||||
padding: $formTBPad 0;
|
padding: $formTBPad 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
//&ng-form {
|
||||||
|
// display: block;
|
||||||
|
//}
|
||||||
|
|
||||||
&.first {
|
&.first {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
@ -171,106 +171,3 @@
|
|||||||
padding: $interiorMargin;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -79,7 +79,6 @@
|
|||||||
|
|
||||||
// Dialog boxes, size constrained and centered in desktop/tablet
|
// Dialog boxes, size constrained and centered in desktop/tablet
|
||||||
&.l-dialog {
|
&.l-dialog {
|
||||||
font-size: 0.8rem;
|
|
||||||
.s-button {
|
.s-button {
|
||||||
&:not(.major) {
|
&:not(.major) {
|
||||||
@include btnSubtle($bg: $colorOvrBtnBg, $bgHov: pullForward($colorOvrBtnBg, 10%), $fg: $colorOvrBtnFg, $fgHov: $colorOvrBtnFg, $ic: $colorOvrBtnFg, $icHov: $colorOvrBtnFg);
|
@include btnSubtle($bg: $colorOvrBtnBg, $bgHov: pullForward($colorOvrBtnBg, 10%), $fg: $colorOvrBtnFg, $fgHov: $colorOvrBtnFg, $ic: $colorOvrBtnFg, $icHov: $colorOvrBtnFg);
|
||||||
@ -126,9 +125,9 @@
|
|||||||
@include containerSubtle($colorOvrBg, $colorOvrFg);
|
@include containerSubtle($colorOvrBg, $colorOvrFg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-title {
|
.title {
|
||||||
@include ellipsize();
|
@include ellipsize();
|
||||||
font-size: 1.5em;
|
font-size: 1.2em;
|
||||||
line-height: 120%;
|
line-height: 120%;
|
||||||
margin-bottom: $interiorMargin;
|
margin-bottom: $interiorMargin;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,6 @@
|
|||||||
|
|
||||||
.gl-plot {
|
.gl-plot {
|
||||||
color: $colorPlotFg;
|
color: $colorPlotFg;
|
||||||
display: flex;
|
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -52,6 +52,7 @@ ul.tree {
|
|||||||
|
|
||||||
.view-control {
|
.view-control {
|
||||||
color: $colorItemTreeVC;
|
color: $colorItemTreeVC;
|
||||||
|
font-size: 0.75em;
|
||||||
margin-right: $interiorMargin;
|
margin-right: $interiorMargin;
|
||||||
width: $treeVCW;
|
width: $treeVCW;
|
||||||
&:before { display: block; }
|
&:before { display: block; }
|
||||||
|
@ -26,10 +26,12 @@
|
|||||||
z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
|
z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
|
||||||
&:not(.no-frame) {
|
&:not(.no-frame) {
|
||||||
background: $colorBodyBg;
|
background: $colorBodyBg;
|
||||||
border-color: $bc;
|
border: 1px solid $bc;
|
||||||
|
&:hover {
|
||||||
|
border-color: lighten($bc, 10%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.object-browse-bar {
|
.object-browse-bar {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
height: $ohH;
|
height: $ohH;
|
||||||
@ -42,8 +44,7 @@
|
|||||||
|
|
||||||
&.t-object-type-timer,
|
&.t-object-type-timer,
|
||||||
&.t-object-type-clock,
|
&.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
|
// Hide the right side buttons for objects where they don't make sense
|
||||||
// Note that this will hide the view Switcher button if applied
|
// Note that this will hide the view Switcher button if applied
|
||||||
// to an object that has it.
|
// to an object that has it.
|
||||||
@ -90,9 +91,9 @@
|
|||||||
|
|
||||||
&.no-frame {
|
&.no-frame {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
border-color: transparent;
|
border: none !important;
|
||||||
.object-browse-bar .right {
|
.object-browse-bar .right {
|
||||||
$m: 0;
|
$m: 0; // $interiorMarginSm;
|
||||||
background: rgba(black, 0.3);
|
background: rgba(black, 0.3);
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
padding: $interiorMarginSm;
|
padding: $interiorMarginSm;
|
||||||
@ -102,7 +103,7 @@
|
|||||||
}
|
}
|
||||||
&.t-frame-outer > .t-rep-frame {
|
&.t-frame-outer > .t-rep-frame {
|
||||||
&.contents {
|
&.contents {
|
||||||
$m: 0px;
|
$m: 2px;
|
||||||
top: $m;
|
top: $m;
|
||||||
right: $m;
|
right: $m;
|
||||||
bottom: $m;
|
bottom: $m;
|
||||||
@ -113,7 +114,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
> .object-holder.abs {
|
> .object-holder.abs {
|
||||||
overflow: hidden;
|
|
||||||
top: 0 !important;
|
top: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,20 +126,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/********************************************************** OBJECT TYPES */
|
/********************************************************** OBJECT TYPES */
|
||||||
.t-object-type-hyperlink,
|
.t-object-type-hyperlink {
|
||||||
.t-object-type-summary-widget {
|
|
||||||
.object-holder {
|
.object-holder {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.w-summary-widget,
|
|
||||||
.l-summary-widget,
|
|
||||||
.l-hyperlink.s-button {
|
.l-hyperlink.s-button {
|
||||||
// Some object types expand to the full size of the object-holder.
|
// When a hyperlink is a button in a frame, make it expand to fill out to the object-holder
|
||||||
@extend .abs;
|
@extend .abs;
|
||||||
}
|
|
||||||
|
|
||||||
.l-summary-widget,
|
|
||||||
.l-hyperlink.s-button {
|
|
||||||
.label {
|
.label {
|
||||||
@include ellipsize();
|
@include ellipsize();
|
||||||
@include transform(translateY(-50%));
|
@include transform(translateY(-50%));
|
||||||
|
@ -20,41 +20,21 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
.s-hover-border {
|
.s-hover-border {
|
||||||
border: 1px solid transparent;
|
border: 1px dotted transparent;
|
||||||
&:hover {
|
|
||||||
border-color: rgba($colorSelectableSelectedPrimary, 0.5) !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-status-editing {
|
.s-status-editing {
|
||||||
// Limit to editing mode
|
// Limit to editing mode until we have sub-object selection
|
||||||
$o: 0.5;
|
|
||||||
$oHover: 0.8;
|
|
||||||
$bc: $colorSelectableSelectedPrimary;
|
|
||||||
.s-hover-border {
|
.s-hover-border {
|
||||||
// Show a border by default so user can see object bounds and empty objects
|
// Show a border by default so user can see object bounds and empty objects
|
||||||
border-color: rgba($bc, $o) !important;
|
border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important;
|
||||||
border-style: dotted !important;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: rgba($bc, $oHover) !important;
|
border-color: rgba($colorSelectableSelectedPrimary, 0.7) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.t-object-type-layout {
|
.s-selected > .s-hover-border,
|
||||||
border-style: dashed !important;
|
.s-selected.s-hover-border {
|
||||||
}
|
|
||||||
}
|
|
||||||
.s-selected {
|
|
||||||
&.s-moveable {
|
|
||||||
&:not(.s-drilled-in) {
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.s-selected > .s-hover-border,
|
|
||||||
.s-selected.s-hover-border {
|
|
||||||
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
|
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
|
||||||
border-color: $colorSelectableSelectedPrimary !important;
|
border-color: $colorSelectableSelectedPrimary !important;
|
||||||
@include boxShdwLarge();
|
@include boxShdwLarge();
|
||||||
@ -65,7 +45,10 @@
|
|||||||
background-color: rgba($colorKey, 1);
|
background-color: rgba($colorKey, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-selected > .s-moveable,
|
||||||
|
.s-selected.s-moveable {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<span ng-controller="DateTimeFieldController">
|
<span ng-controller="DateTimeFieldController">
|
||||||
<input type="text" autocorrect="off" spellcheck="false"
|
<input type="text"
|
||||||
ng-model="textValue"
|
ng-model="textValue"
|
||||||
ng-blur="restoreTextValue(); ngBlur()"
|
ng-blur="restoreTextValue(); ngBlur()"
|
||||||
ng-mouseup="ngMouseup()"
|
ng-mouseup="ngMouseup()"
|
||||||
|
@ -40,7 +40,7 @@ define(
|
|||||||
|
|
||||||
// Gets an array of the contextual parents/ancestors of the selected object
|
// Gets an array of the contextual parents/ancestors of the selected object
|
||||||
function getContextualPath() {
|
function getContextualPath() {
|
||||||
var currentObj = $scope.domainObject,
|
var currentObj = $scope.ngModel.selectedObject,
|
||||||
currentParent,
|
currentParent,
|
||||||
parents = [];
|
parents = [];
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ define(
|
|||||||
|
|
||||||
// If this the the initial call of this recursive function
|
// If this the the initial call of this recursive function
|
||||||
if (!current) {
|
if (!current) {
|
||||||
current = $scope.domainObject;
|
current = $scope.ngModel.selectedObject;
|
||||||
$scope.primaryParents = [];
|
$scope.primaryParents = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,16 +87,16 @@ define(
|
|||||||
|
|
||||||
// Gets the metadata for the selected object
|
// Gets the metadata for the selected object
|
||||||
function getMetadata() {
|
function getMetadata() {
|
||||||
$scope.metadata = $scope.domainObject &&
|
$scope.metadata = $scope.ngModel.selectedObject &&
|
||||||
$scope.domainObject.hasCapability('metadata') &&
|
$scope.ngModel.selectedObject.hasCapability('metadata') &&
|
||||||
$scope.domainObject.useCapability('metadata');
|
$scope.ngModel.selectedObject.useCapability('metadata');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set scope variables when the selected object changes
|
// Set scope variables when the selected object changes
|
||||||
$scope.$watch('domainObject', function () {
|
$scope.$watch('ngModel.selectedObject', function () {
|
||||||
$scope.isLink = $scope.domainObject &&
|
$scope.isLink = $scope.ngModel.selectedObject &&
|
||||||
$scope.domainObject.hasCapability('location') &&
|
$scope.ngModel.selectedObject.hasCapability('location') &&
|
||||||
$scope.domainObject.getCapability('location').isLink();
|
$scope.ngModel.selectedObject.getCapability('location').isLink();
|
||||||
|
|
||||||
if ($scope.isLink) {
|
if ($scope.isLink) {
|
||||||
getPrimaryPath();
|
getPrimaryPath();
|
||||||
@ -109,7 +109,7 @@ define(
|
|||||||
getMetadata();
|
getMetadata();
|
||||||
});
|
});
|
||||||
|
|
||||||
var mutation = $scope.domainObject.getCapability('mutation');
|
var mutation = $scope.ngModel.selectedObject.getCapability('mutation');
|
||||||
var unlisten = mutation.listen(getMetadata);
|
var unlisten = mutation.listen(getMetadata);
|
||||||
$scope.$on('$destroy', unlisten);
|
$scope.$on('$destroy', unlisten);
|
||||||
}
|
}
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
);
|
|
@ -41,6 +41,16 @@ define(
|
|||||||
"$scope",
|
"$scope",
|
||||||
["$watch", "$on"]
|
["$watch", "$on"]
|
||||||
);
|
);
|
||||||
|
mockScope.ngModel = {};
|
||||||
|
mockScope.ngModel.selectedObject = {
|
||||||
|
getCapability: function () {
|
||||||
|
return {
|
||||||
|
listen: function () {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
mockObjectService = jasmine.createSpyObj(
|
mockObjectService = jasmine.createSpyObj(
|
||||||
"objectService",
|
"objectService",
|
||||||
@ -67,27 +77,22 @@ define(
|
|||||||
"location capability",
|
"location capability",
|
||||||
["isLink"]
|
["isLink"]
|
||||||
);
|
);
|
||||||
|
|
||||||
mockDomainObject.getCapability.andCallFake(function (param) {
|
mockDomainObject.getCapability.andCallFake(function (param) {
|
||||||
if (param === 'location') {
|
if (param === 'location') {
|
||||||
return mockLocationCapability;
|
return mockLocationCapability;
|
||||||
} else if (param === 'context') {
|
} else if (param === 'context') {
|
||||||
return mockContextCapability;
|
return mockContextCapability;
|
||||||
} else if (param === 'mutation') {
|
|
||||||
return {
|
|
||||||
listen: function () {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
controller = new ObjectInspectorController(mockScope, mockObjectService);
|
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 () {
|
it("watches for changes to the selected object", function () {
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
|
expect(mockScope.$watch).toHaveBeenCalledWith('ngModel.selectedObject', jasmine.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("looks for contextual parent objects", function () {
|
it("looks for contextual parent objects", function () {
|
||||||
|
@ -65,10 +65,6 @@ define(
|
|||||||
options = Object.create(OPTIONS);
|
options = Object.create(OPTIONS);
|
||||||
options.marginX = -bubbleSpaceLR;
|
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,
|
// On a phone, bubble takes up more screen real estate,
|
||||||
// so position it differently (toward the bottom)
|
// so position it differently (toward the bottom)
|
||||||
if (this.agentService.isPhone()) {
|
if (this.agentService.isPhone()) {
|
||||||
|
@ -38,8 +38,7 @@ define([
|
|||||||
"implementation": InspectorController,
|
"implementation": InspectorController,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$scope",
|
"$scope",
|
||||||
"openmct",
|
"policyService"
|
||||||
"$document"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -21,72 +21,43 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
[],
|
['../../browse/src/InspectorRegion'],
|
||||||
function () {
|
function (InspectorRegion) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InspectorController listens for the selection changes and adds the selection
|
* The InspectorController adds region data for a domain object's type
|
||||||
* object to the scope.
|
* to the scope.
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function InspectorController($scope, openmct, $document) {
|
function InspectorController($scope, policyService) {
|
||||||
var self = this;
|
var domainObject = $scope.domainObject,
|
||||||
self.$scope = $scope;
|
typeCapability = domainObject.getCapability('type'),
|
||||||
|
statusListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback handler for the selection change event.
|
* Filters region parts to only those allowed by region policies
|
||||||
* Adds the selection object to the scope. If the selected item has an inspector view,
|
* @param regions
|
||||||
* it puts the key in the scope. If provider view exists, it shows the view.
|
* @returns {{}}
|
||||||
*/
|
*/
|
||||||
function setSelection(selection) {
|
function filterRegions(inspector) {
|
||||||
if (selection[0]) {
|
//Dupe so we're not modifying the type definition.
|
||||||
var view = openmct.inspectorViews.get(selection);
|
return inspector.regions && inspector.regions.filter(function (region) {
|
||||||
var container = $document[0].querySelectorAll('.inspector-provider-view')[0];
|
return policyService.allow('region', region, domainObject);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$scope.selection = selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
openmct.selection.on("change", setSelection);
|
|
||||||
|
|
||||||
setSelection(openmct.selection.get());
|
|
||||||
|
|
||||||
$scope.$on("$destroy", function () {
|
|
||||||
openmct.selection.off("change", setSelection);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function setRegions() {
|
||||||
* Gets the selected item.
|
$scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
|
||||||
*
|
}
|
||||||
* @returns a domain object
|
|
||||||
*/
|
|
||||||
InspectorController.prototype.selectedItem = function () {
|
|
||||||
return this.$scope.selection[0].context.oldItem;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
statusListener = domainObject.getCapability("status").listen(setRegions);
|
||||||
* Checks if a provider view exists.
|
$scope.$on("$destroy", function () {
|
||||||
*
|
statusListener();
|
||||||
* @returns 'true' if provider view exists, 'false' otherwise
|
});
|
||||||
*/
|
|
||||||
InspectorController.prototype.hasProviderView = function () {
|
setRegions();
|
||||||
return this.providerView;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
return InspectorController;
|
return InspectorController;
|
||||||
}
|
}
|
||||||
|
@ -27,93 +27,82 @@ define(
|
|||||||
describe("The inspector controller ", function () {
|
describe("The inspector controller ", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
mockOpenMCT,
|
mockTypeCapability,
|
||||||
mockSelection,
|
mockTypeDefinition,
|
||||||
mockInspectorViews,
|
mockPolicyService,
|
||||||
mockTypeDef,
|
mockStatusCapability,
|
||||||
controller,
|
capabilities = {},
|
||||||
container,
|
controller;
|
||||||
$document = [],
|
|
||||||
selectable = [];
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockTypeDef = {
|
mockTypeDefinition = {
|
||||||
typeDef: {
|
inspector:
|
||||||
inspector: "some-key"
|
{
|
||||||
|
'regions': [
|
||||||
|
{'name': 'Part One'},
|
||||||
|
{'name': 'Part Two'}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mockTypeCapability = jasmine.createSpyObj('typeCapability', [
|
||||||
|
'getDefinition'
|
||||||
|
]);
|
||||||
|
mockTypeCapability.getDefinition.andReturn(mockTypeDefinition);
|
||||||
|
capabilities.type = mockTypeCapability;
|
||||||
|
|
||||||
|
mockStatusCapability = jasmine.createSpyObj('statusCapability', [
|
||||||
|
'listen'
|
||||||
|
]);
|
||||||
|
capabilities.status = mockStatusCapability;
|
||||||
|
|
||||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
||||||
'getCapability'
|
'getCapability'
|
||||||
]);
|
]);
|
||||||
mockDomainObject.getCapability.andReturn(mockTypeDef);
|
mockDomainObject.getCapability.andCallFake(function (name) {
|
||||||
|
return capabilities[name];
|
||||||
|
});
|
||||||
|
|
||||||
|
mockPolicyService = jasmine.createSpyObj('policyService', [
|
||||||
|
'allow'
|
||||||
|
]);
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj('$scope',
|
mockScope = jasmine.createSpyObj('$scope',
|
||||||
['$on', 'selection']
|
['$on']
|
||||||
);
|
);
|
||||||
|
|
||||||
selectable[0] = {
|
mockScope.domainObject = mockDomainObject;
|
||||||
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("listens for selection change event", function () {
|
it("filters out regions disallowed by region policy", function () {
|
||||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
mockPolicyService.allow.andReturn(false);
|
||||||
'change',
|
controller = new InspectorController(mockScope, mockPolicyService);
|
||||||
jasmine.any(Function)
|
expect(mockScope.regions.length).toBe(0);
|
||||||
);
|
|
||||||
|
|
||||||
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("cleans up on scope destroy", function () {
|
it("does not filter out regions allowed by region policy", function () {
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
mockPolicyService.allow.andReturn(true);
|
||||||
'$destroy',
|
controller = new InspectorController(mockScope, mockPolicyService);
|
||||||
jasmine.any(Function)
|
expect(mockScope.regions.length).toBe(2);
|
||||||
);
|
|
||||||
|
|
||||||
mockScope.$on.calls[0].args[1]();
|
|
||||||
|
|
||||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds selection object to scope", function () {
|
it("Responds to status changes", function () {
|
||||||
expect(mockScope.selection).toEqual(selectable);
|
mockPolicyService.allow.andReturn(true);
|
||||||
expect(controller.selectedItem()).toEqual(mockDomainObject);
|
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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -245,12 +245,6 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
|||||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||||
$colorCalCellInMonthBg: pushBack($colorMenuBg, 5%);
|
$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
|
// About Screen
|
||||||
$colorAboutLink: #84b3ff;
|
$colorAboutLink: #84b3ff;
|
||||||
|
|
||||||
|
@ -245,12 +245,6 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
|||||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||||
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
|
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
|
||||||
|
|
||||||
// Palettes
|
|
||||||
$colorPaletteFg: pullForward($colorMenuBg, 30%);
|
|
||||||
$colorPaletteSelected: #333;
|
|
||||||
$shdwPaletteFg: none;
|
|
||||||
$shdwPaletteSelected: inset 0 0 0 1px #fff;
|
|
||||||
|
|
||||||
// About Screen
|
// About Screen
|
||||||
$colorAboutLink: #84b3ff;
|
$colorAboutLink: #84b3ff;
|
||||||
|
|
||||||
|
54
platform/features/autoflow/plugin.js
Executable file
54
platform/features/autoflow/plugin.js
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
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");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
26
platform/features/autoflow/res/templates/autoflow-tabular.html
Executable file
26
platform/features/autoflow/res/templates/autoflow-tabular.html
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
<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>
|
169
platform/features/autoflow/src/AutoflowTableLinker.js
Executable file
169
platform/features/autoflow/src/AutoflowTableLinker.js
Executable file
@ -0,0 +1,169 @@
|
|||||||
|
/*global angular*/
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The link step for the `mct-autoflow-table` directive;
|
||||||
|
* watches scope and updates the DOM appropriately.
|
||||||
|
* See documentation in `MCTAutoflowTable.js` for the rationale
|
||||||
|
* for including this directive, as well as for an explanation
|
||||||
|
* of which values are placed in scope.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Scope} scope the scope for this usage of the directive
|
||||||
|
* @param element the jqLite-wrapped element which used this directive
|
||||||
|
*/
|
||||||
|
function AutoflowTableLinker(scope, element) {
|
||||||
|
var objects, // Domain objects at last structure refresh
|
||||||
|
rows, // Number of rows from last structure refresh
|
||||||
|
priorClasses = {},
|
||||||
|
valueSpans = {}; // Span elements to put data values in
|
||||||
|
|
||||||
|
// Create a new name-value pair in the specified column
|
||||||
|
function createListItem(domainObject, ul) {
|
||||||
|
// Create a new li, and spans to go in it.
|
||||||
|
var li = angular.element('<li>'),
|
||||||
|
titleSpan = angular.element('<span>'),
|
||||||
|
valueSpan = angular.element('<span>');
|
||||||
|
|
||||||
|
// Place spans in the li, and li into the column.
|
||||||
|
// valueSpan must precede titleSpan in the DOM due to new CSS float approach
|
||||||
|
li.append(valueSpan).append(titleSpan);
|
||||||
|
ul.append(li);
|
||||||
|
|
||||||
|
// Style appropriately
|
||||||
|
li.addClass('l-autoflow-row');
|
||||||
|
titleSpan.addClass('l-autoflow-item l');
|
||||||
|
valueSpan.addClass('l-autoflow-item r l-obj-val-format');
|
||||||
|
|
||||||
|
// Set text/tooltip for the name-value row
|
||||||
|
titleSpan.text(domainObject.getModel().name);
|
||||||
|
titleSpan.attr("title", domainObject.getModel().name);
|
||||||
|
|
||||||
|
// Keep a reference to the span which will hold the
|
||||||
|
// data value, to populate in the next refreshValues call
|
||||||
|
valueSpans[domainObject.getId()] = valueSpan;
|
||||||
|
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new column of name-value pairs in this table.
|
||||||
|
function createColumn(el) {
|
||||||
|
// Create a ul
|
||||||
|
var ul = angular.element('<ul>');
|
||||||
|
|
||||||
|
// Add it into the mct-autoflow-table
|
||||||
|
el.append(ul);
|
||||||
|
|
||||||
|
// Style appropriately
|
||||||
|
ul.addClass('l-autoflow-col');
|
||||||
|
|
||||||
|
// Get the current col width and apply at time of column creation
|
||||||
|
// Important to do this here, as new columns could be created after
|
||||||
|
// the user has changed the width.
|
||||||
|
ul.css('width', scope.columnWidth + 'px');
|
||||||
|
|
||||||
|
// Return it, so some li elements can be added
|
||||||
|
return ul;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the width of the columns when user clicks the resize button.
|
||||||
|
function resizeColumn() {
|
||||||
|
element.find('ul').css('width', scope.columnWidth + 'px');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild the DOM associated with this table.
|
||||||
|
function rebuild(domainObjects, rowCount) {
|
||||||
|
var activeColumn;
|
||||||
|
|
||||||
|
// Empty out our cached span elements
|
||||||
|
valueSpans = {};
|
||||||
|
|
||||||
|
// Start with an empty DOM beneath this directive
|
||||||
|
element.html("");
|
||||||
|
|
||||||
|
// Add DOM elements for each domain object being displayed
|
||||||
|
// in this table.
|
||||||
|
domainObjects.forEach(function (object, index) {
|
||||||
|
// Start a new column if we'd run out of room
|
||||||
|
if (index % rowCount === 0) {
|
||||||
|
activeColumn = createColumn(element);
|
||||||
|
}
|
||||||
|
// Add the DOM elements for that object to whichever
|
||||||
|
// column (a `ul` element) is current.
|
||||||
|
createListItem(object, activeColumn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update spans with values, as made available via the
|
||||||
|
// `values` attribute of this directive.
|
||||||
|
function refreshValues() {
|
||||||
|
// Get the available values
|
||||||
|
var values = scope.values || {},
|
||||||
|
classes = scope.classes || {};
|
||||||
|
|
||||||
|
// Populate all spans with those values (or clear
|
||||||
|
// those spans if no value is available)
|
||||||
|
(objects || []).forEach(function (object) {
|
||||||
|
var id = object.getId(),
|
||||||
|
span = valueSpans[id],
|
||||||
|
value;
|
||||||
|
|
||||||
|
if (span) {
|
||||||
|
// Look up the value...
|
||||||
|
value = values[id];
|
||||||
|
// ...and convert to empty string if it's undefined
|
||||||
|
value = value === undefined ? "" : value;
|
||||||
|
span.attr("data-value", value);
|
||||||
|
|
||||||
|
// Update the span
|
||||||
|
span.text(value);
|
||||||
|
span.attr("title", value);
|
||||||
|
span.removeClass(priorClasses[id]);
|
||||||
|
span.addClass(classes[id]);
|
||||||
|
priorClasses[id] = classes[id];
|
||||||
|
}
|
||||||
|
// Also need stale/alert/ok class
|
||||||
|
// on span
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the DOM for this table, if necessary
|
||||||
|
function refreshStructure() {
|
||||||
|
// Only rebuild if number of rows or set of objects
|
||||||
|
// has changed; otherwise, our structure is still valid.
|
||||||
|
if (scope.objects !== objects ||
|
||||||
|
scope.rows !== rows) {
|
||||||
|
|
||||||
|
// Track those values to support future refresh checks
|
||||||
|
objects = scope.objects;
|
||||||
|
rows = scope.rows;
|
||||||
|
|
||||||
|
// Rebuild the DOM
|
||||||
|
rebuild(objects || [], rows || 1);
|
||||||
|
|
||||||
|
// Refresh all data values shown
|
||||||
|
refreshValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changing the domain objects in use or the number
|
||||||
|
// of rows should trigger a structure change (DOM rebuild)
|
||||||
|
scope.$watch("objects", refreshStructure);
|
||||||
|
scope.$watch("rows", refreshStructure);
|
||||||
|
|
||||||
|
// When the current column width has been changed, resize the column
|
||||||
|
scope.$watch('columnWidth', resizeColumn);
|
||||||
|
|
||||||
|
// When the last-updated time ticks,
|
||||||
|
scope.$watch("updated", refreshValues);
|
||||||
|
|
||||||
|
// Update displayed values when the counter changes.
|
||||||
|
scope.$watch("counter", refreshValues);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return AutoflowTableLinker;
|
||||||
|
}
|
||||||
|
);
|
324
platform/features/autoflow/src/AutoflowTabularController.js
Executable file
324
platform/features/autoflow/src/AutoflowTabularController.js
Executable file
@ -0,0 +1,324 @@
|
|||||||
|
|
||||||
|
define(
|
||||||
|
['moment'],
|
||||||
|
function (moment) {
|
||||||
|
|
||||||
|
var ROW_HEIGHT = 16,
|
||||||
|
SLIDER_HEIGHT = 10,
|
||||||
|
INITIAL_COLUMN_WIDTH = 225,
|
||||||
|
MAX_COLUMN_WIDTH = 525,
|
||||||
|
COLUMN_WIDTH_STEP = 25,
|
||||||
|
DEBOUNCE_INTERVAL = 100,
|
||||||
|
DATE_FORMAT = "YYYY-DDD HH:mm:ss.SSS\\Z",
|
||||||
|
NOT_UPDATED = "No updates",
|
||||||
|
EMPTY_ARRAY = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for supporting the autoflow tabular view.
|
||||||
|
* Implements the all-over logic which drives that view,
|
||||||
|
* mediating between template-provided areas, the included
|
||||||
|
* `mct-autoflow-table` directive, and the underlying
|
||||||
|
* domain object model.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function AutflowTabularController(
|
||||||
|
$scope,
|
||||||
|
$timeout,
|
||||||
|
telemetrySubscriber
|
||||||
|
) {
|
||||||
|
var filterValue = "",
|
||||||
|
filterValueLowercase = "",
|
||||||
|
subscription,
|
||||||
|
filteredObjects = [],
|
||||||
|
lastUpdated = {},
|
||||||
|
updateText = NOT_UPDATED,
|
||||||
|
rangeValues = {},
|
||||||
|
classes = {},
|
||||||
|
limits = {},
|
||||||
|
updatePending = false,
|
||||||
|
lastBounce = Number.NEGATIVE_INFINITY,
|
||||||
|
columnWidth = INITIAL_COLUMN_WIDTH,
|
||||||
|
rows = 1,
|
||||||
|
counter = 0;
|
||||||
|
|
||||||
|
// Trigger an update of the displayed table by incrementing
|
||||||
|
// the counter that it watches.
|
||||||
|
function triggerDisplayUpdate() {
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether or not an object's name matches the
|
||||||
|
// user-entered filter value.
|
||||||
|
function filterObject(domainObject) {
|
||||||
|
return (domainObject.getModel().name || "")
|
||||||
|
.toLowerCase()
|
||||||
|
.indexOf(filterValueLowercase) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparator for sorting points back into packet order
|
||||||
|
function compareObject(objectA, objectB) {
|
||||||
|
var indexA = objectA.getModel().index || 0,
|
||||||
|
indexB = objectB.getModel().index || 0;
|
||||||
|
return indexA - indexB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the list of currently-displayed objects; these
|
||||||
|
// will be the subset of currently subscribed-to objects
|
||||||
|
// which match a user-entered filter.
|
||||||
|
function doUpdateFilteredObjects() {
|
||||||
|
// Generate the list
|
||||||
|
filteredObjects = (
|
||||||
|
subscription ?
|
||||||
|
subscription.getTelemetryObjects() :
|
||||||
|
[]
|
||||||
|
).filter(filterObject).sort(compareObject);
|
||||||
|
|
||||||
|
// Clear the pending flag
|
||||||
|
updatePending = false;
|
||||||
|
|
||||||
|
// Track when this occurred, so that we can wait
|
||||||
|
// a whole before updating again.
|
||||||
|
lastBounce = Date.now();
|
||||||
|
|
||||||
|
triggerDisplayUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request an update to the list of current objects; this may
|
||||||
|
// run on a timeout to avoid excessive calls, e.g. while the user
|
||||||
|
// is typing a filter.
|
||||||
|
function updateFilteredObjects() {
|
||||||
|
// Don't do anything if an update is already scheduled
|
||||||
|
if (!updatePending) {
|
||||||
|
if (Date.now() > lastBounce + DEBOUNCE_INTERVAL) {
|
||||||
|
// Update immediately if it's been long enough
|
||||||
|
doUpdateFilteredObjects();
|
||||||
|
} else {
|
||||||
|
// Otherwise, update later, and track that we have
|
||||||
|
// an update pending so that subsequent calls can
|
||||||
|
// be ignored.
|
||||||
|
updatePending = true;
|
||||||
|
$timeout(doUpdateFilteredObjects, DEBOUNCE_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the latest data values for this domain object
|
||||||
|
function recordData(telemetryObject) {
|
||||||
|
// Get latest domain/range values for this object.
|
||||||
|
var id = telemetryObject.getId(),
|
||||||
|
domainValue = subscription.getDomainValue(telemetryObject),
|
||||||
|
rangeValue = subscription.getRangeValue(telemetryObject);
|
||||||
|
|
||||||
|
// Track the most recent timestamp change observed...
|
||||||
|
if (domainValue !== undefined && domainValue !== lastUpdated[id]) {
|
||||||
|
lastUpdated[id] = domainValue;
|
||||||
|
// ... and update the displayable text for that timestamp
|
||||||
|
updateText = isNaN(domainValue) ? "" :
|
||||||
|
moment.utc(domainValue).format(DATE_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store data values into the rangeValues structure, which
|
||||||
|
// will be used to populate the table itself.
|
||||||
|
// Note that we want full precision here.
|
||||||
|
rangeValues[id] = rangeValue;
|
||||||
|
|
||||||
|
// Update limit states as well
|
||||||
|
classes[id] = limits[id] && (limits[id].evaluate({
|
||||||
|
// This relies on external knowledge that the
|
||||||
|
// range value of a telemetry point is encoded
|
||||||
|
// in its datum as "value."
|
||||||
|
value: rangeValue
|
||||||
|
}) || {}).cssClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Look at telemetry objects from the subscription; this is watched
|
||||||
|
// to detect changes from the subscription.
|
||||||
|
function subscribedTelemetry() {
|
||||||
|
return subscription ?
|
||||||
|
subscription.getTelemetryObjects() : EMPTY_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the data values which will be used to populate the table
|
||||||
|
function updateValues() {
|
||||||
|
subscribedTelemetry().forEach(recordData);
|
||||||
|
triggerDisplayUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter-setter function for user-entered filter text.
|
||||||
|
function filter(value) {
|
||||||
|
// If value was specified, we're a setter
|
||||||
|
if (value !== undefined) {
|
||||||
|
// Store the new value
|
||||||
|
filterValue = value;
|
||||||
|
filterValueLowercase = value.toLowerCase();
|
||||||
|
// Change which objects appear in the table
|
||||||
|
updateFilteredObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always act as a getter
|
||||||
|
return filterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the bounds (width and height) of this view;
|
||||||
|
// called from the mct-resize directive. Recalculates how
|
||||||
|
// many rows should appear in the contained table.
|
||||||
|
function setBounds(bounds) {
|
||||||
|
var availableSpace = bounds.height - SLIDER_HEIGHT;
|
||||||
|
rows = Math.max(1, Math.floor(availableSpace / ROW_HEIGHT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the current column width, up to the defined maximum.
|
||||||
|
// When the max is hit, roll back to the default.
|
||||||
|
function increaseColumnWidth() {
|
||||||
|
columnWidth += COLUMN_WIDTH_STEP;
|
||||||
|
// Cycle down to the initial width instead of exceeding max
|
||||||
|
columnWidth = columnWidth > MAX_COLUMN_WIDTH ?
|
||||||
|
INITIAL_COLUMN_WIDTH : columnWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get displayable text for last-updated value
|
||||||
|
function updated() {
|
||||||
|
return updateText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe, if a subscription is active.
|
||||||
|
function releaseSubscription() {
|
||||||
|
if (subscription) {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
subscription = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update set of telemetry objects managed by this view
|
||||||
|
function updateTelemetryObjects(telemetryObjects) {
|
||||||
|
updateFilteredObjects();
|
||||||
|
limits = {};
|
||||||
|
telemetryObjects.forEach(function (telemetryObject) {
|
||||||
|
var id = telemetryObject.getId();
|
||||||
|
limits[id] = telemetryObject.getCapability('limit');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a subscription for the represented domain object.
|
||||||
|
// This will resolve capability delegation as necessary.
|
||||||
|
function makeSubscription(domainObject) {
|
||||||
|
// Unsubscribe, if there is an existing subscription
|
||||||
|
releaseSubscription();
|
||||||
|
|
||||||
|
// Clear updated timestamp
|
||||||
|
lastUpdated = {};
|
||||||
|
updateText = NOT_UPDATED;
|
||||||
|
|
||||||
|
// Create a new subscription; telemetrySubscriber gets
|
||||||
|
// to do the meaningful work here.
|
||||||
|
subscription = domainObject && telemetrySubscriber.subscribe(
|
||||||
|
domainObject,
|
||||||
|
updateValues
|
||||||
|
);
|
||||||
|
|
||||||
|
// Our set of in-view telemetry objects may have changed,
|
||||||
|
// so update the set that is being passed down to the table.
|
||||||
|
updateFilteredObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for changes to the set of objects which have telemetry
|
||||||
|
$scope.$watch(subscribedTelemetry, updateTelemetryObjects);
|
||||||
|
|
||||||
|
// Watch for the represented domainObject (this field will
|
||||||
|
// be populated by mct-representation)
|
||||||
|
$scope.$watch("domainObject", makeSubscription);
|
||||||
|
|
||||||
|
// Make sure we unsubscribe when this view is destroyed.
|
||||||
|
$scope.$on("$destroy", releaseSubscription);
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get the number of rows which should be shown in this table.
|
||||||
|
* @return {number} the number of rows to show
|
||||||
|
*/
|
||||||
|
getRows: function () {
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the objects which should currently be displayed in
|
||||||
|
* this table. This will be watched, so the return value
|
||||||
|
* should be stable when this list is unchanging. Only
|
||||||
|
* objects which match the user-entered filter value should
|
||||||
|
* be returned here.
|
||||||
|
* @return {DomainObject[]} the domain objects to include in
|
||||||
|
* this table.
|
||||||
|
*/
|
||||||
|
getTelemetryObjects: function () {
|
||||||
|
return filteredObjects;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Set the bounds (width/height) of this autoflow tabular view.
|
||||||
|
* The template must ensure that these bounds are tracked on
|
||||||
|
* the table area only.
|
||||||
|
* @param bounds the bounds; and object with `width` and
|
||||||
|
* `height` properties, both as numbers, in pixels.
|
||||||
|
*/
|
||||||
|
setBounds: setBounds,
|
||||||
|
/**
|
||||||
|
* Increments the width of the autoflow column.
|
||||||
|
* Setting does not yet persist.
|
||||||
|
*/
|
||||||
|
increaseColumnWidth: increaseColumnWidth,
|
||||||
|
/**
|
||||||
|
* Get-or-set the user-supplied filter value.
|
||||||
|
* @param {string} [value] the new filter value; omit to use
|
||||||
|
* as a getter
|
||||||
|
* @returns {string} the user-supplied filter value
|
||||||
|
*/
|
||||||
|
filter: filter,
|
||||||
|
/**
|
||||||
|
* Get all range values for use in this table. These will be
|
||||||
|
* returned as an object of key-value pairs, where keys are
|
||||||
|
* domain object IDs, and values are the most recently observed
|
||||||
|
* data values associated with those objects, formatted for
|
||||||
|
* display.
|
||||||
|
* @returns {object.<string,string>} most recent values
|
||||||
|
*/
|
||||||
|
rangeValues: function () {
|
||||||
|
return rangeValues;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get CSS classes to apply to specific rows, representing limit
|
||||||
|
* states and/or stale states. These are returned as key-value
|
||||||
|
* pairs where keys are domain object IDs, and values are CSS
|
||||||
|
* classes to display for domain objects with those IDs.
|
||||||
|
* @returns {object.<string,string>} CSS classes
|
||||||
|
*/
|
||||||
|
classes: function () {
|
||||||
|
return classes;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the "last updated" text for this view; this will be
|
||||||
|
* the most recent timestamp observed for any telemetry-
|
||||||
|
* providing object, formatted for display.
|
||||||
|
* @returns {string} the time of the most recent update
|
||||||
|
*/
|
||||||
|
updated: updated,
|
||||||
|
/**
|
||||||
|
* Get the current column width, in pixels.
|
||||||
|
* @returns {number} column width
|
||||||
|
*/
|
||||||
|
columnWidth: function () {
|
||||||
|
return columnWidth;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Keep a counter and increment this whenever the display
|
||||||
|
* should be updated; this will be watched by the
|
||||||
|
* `mct-autoflow-table`.
|
||||||
|
* @returns {number} a counter value
|
||||||
|
*/
|
||||||
|
counter: function () {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return AutflowTabularController;
|
||||||
|
}
|
||||||
|
);
|
60
platform/features/autoflow/src/MCTAutoflowTable.js
Executable file
60
platform/features/autoflow/src/MCTAutoflowTable.js
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
define(
|
||||||
|
["./AutoflowTableLinker"],
|
||||||
|
function (AutoflowTableLinker) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `mct-autoflow-table` directive specifically supports
|
||||||
|
* autoflow tabular views; it is not intended for use outside
|
||||||
|
* of that view.
|
||||||
|
*
|
||||||
|
* This directive is responsible for creating the structure
|
||||||
|
* of the table in this view, and for updating its values.
|
||||||
|
* While this is achievable using a regular Angular template,
|
||||||
|
* this is undesirable from the perspective of performance
|
||||||
|
* due to the number of watches that can be involved for large
|
||||||
|
* tables. Instead, this directive will maintain a small number
|
||||||
|
* of watches, rebuilding table structure only when necessary,
|
||||||
|
* and updating displayed values in the more common case of
|
||||||
|
* new data arriving.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function MCTAutoflowTable() {
|
||||||
|
return {
|
||||||
|
// Only applicable at the element level
|
||||||
|
restrict: "E",
|
||||||
|
|
||||||
|
// The link function; handles DOM update/manipulation
|
||||||
|
link: AutoflowTableLinker,
|
||||||
|
|
||||||
|
// Parameters to pass from attributes into scope
|
||||||
|
scope: {
|
||||||
|
// Set of domain objects to show in the table
|
||||||
|
objects: "=",
|
||||||
|
|
||||||
|
// Values for those objects, by ID
|
||||||
|
values: "=",
|
||||||
|
|
||||||
|
// CSS classes to show for objects, by ID
|
||||||
|
classes: "=",
|
||||||
|
|
||||||
|
// Number of rows to show before autoflowing
|
||||||
|
rows: "=",
|
||||||
|
|
||||||
|
// Time of last update; watched to refresh values
|
||||||
|
updated: "=",
|
||||||
|
|
||||||
|
// Current width of the autoflow column
|
||||||
|
columnWidth: "=",
|
||||||
|
|
||||||
|
// A counter used to trigger display updates
|
||||||
|
counter: "="
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MCTAutoflowTable;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
178
platform/features/autoflow/test/AutoflowTableLinkerSpec.js
Executable file
178
platform/features/autoflow/test/AutoflowTableLinkerSpec.js
Executable file
@ -0,0 +1,178 @@
|
|||||||
|
|
||||||
|
define(
|
||||||
|
["../src/AutoflowTableLinker"],
|
||||||
|
function (AutoflowTableLinker) {
|
||||||
|
|
||||||
|
describe("The mct-autoflow-table linker", function () {
|
||||||
|
var cachedAngular,
|
||||||
|
mockAngular,
|
||||||
|
mockScope,
|
||||||
|
mockElement,
|
||||||
|
mockElements,
|
||||||
|
linker;
|
||||||
|
|
||||||
|
// Utility function to generate more mock elements
|
||||||
|
function createMockElement(html) {
|
||||||
|
var mockEl = jasmine.createSpyObj(
|
||||||
|
"element-" + html,
|
||||||
|
[
|
||||||
|
"append",
|
||||||
|
"addClass",
|
||||||
|
"removeClass",
|
||||||
|
"text",
|
||||||
|
"attr",
|
||||||
|
"html",
|
||||||
|
"css",
|
||||||
|
"find"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockEl.testHtml = html;
|
||||||
|
mockEl.append.andReturn(mockEl);
|
||||||
|
mockElements.push(mockEl);
|
||||||
|
return mockEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockDomainObject(id) {
|
||||||
|
var mockDomainObject = jasmine.createSpyObj(
|
||||||
|
"domainObject-" + id,
|
||||||
|
["getId", "getModel"]
|
||||||
|
);
|
||||||
|
mockDomainObject.getId.andReturn(id);
|
||||||
|
mockDomainObject.getModel.andReturn({name: id.toUpperCase()});
|
||||||
|
return mockDomainObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fireWatch(watchExpression, value) {
|
||||||
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === watchExpression) {
|
||||||
|
call.args[1](value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoflowTableLinker accesses Angular in the global
|
||||||
|
// scope, since it is not injectable; we simulate that
|
||||||
|
// here by adding/removing it to/from the window object.
|
||||||
|
beforeEach(function () {
|
||||||
|
mockElements = [];
|
||||||
|
|
||||||
|
mockAngular = jasmine.createSpyObj("angular", ["element"]);
|
||||||
|
mockScope = jasmine.createSpyObj("scope", ["$watch"]);
|
||||||
|
mockElement = createMockElement('<div>');
|
||||||
|
|
||||||
|
mockAngular.element.andCallFake(createMockElement);
|
||||||
|
|
||||||
|
if (window.angular !== undefined) {
|
||||||
|
cachedAngular = window.angular;
|
||||||
|
}
|
||||||
|
window.angular = mockAngular;
|
||||||
|
|
||||||
|
linker = new AutoflowTableLinker(mockScope, mockElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
if (cachedAngular !== undefined) {
|
||||||
|
window.angular = cachedAngular;
|
||||||
|
} else {
|
||||||
|
delete window.angular;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("watches for changes in inputs", function () {
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"objects",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"rows",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"counter",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes structure when domain objects change", function () {
|
||||||
|
// Set up scope
|
||||||
|
mockScope.rows = 4;
|
||||||
|
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||||
|
.map(createMockDomainObject);
|
||||||
|
|
||||||
|
// Fire an update to the set of objects
|
||||||
|
fireWatch("objects");
|
||||||
|
|
||||||
|
// Should have rebuilt with two columns of
|
||||||
|
// four and two rows each; first, by clearing...
|
||||||
|
expect(mockElement.html).toHaveBeenCalledWith("");
|
||||||
|
|
||||||
|
// Should have appended two columns...
|
||||||
|
expect(mockElement.append.calls.length).toEqual(2);
|
||||||
|
|
||||||
|
// ...which should have received two and four rows each
|
||||||
|
expect(mockElement.append.calls[0].args[0].append.calls.length)
|
||||||
|
.toEqual(4);
|
||||||
|
expect(mockElement.append.calls[1].args[0].append.calls.length)
|
||||||
|
.toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates values", function () {
|
||||||
|
var mockSpans;
|
||||||
|
|
||||||
|
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||||
|
.map(createMockDomainObject);
|
||||||
|
mockScope.values = { a: 0 };
|
||||||
|
|
||||||
|
// Fire an update to the set of values
|
||||||
|
fireWatch("objects");
|
||||||
|
fireWatch("updated");
|
||||||
|
|
||||||
|
// Get all created spans
|
||||||
|
mockSpans = mockElements.filter(function (mockElem) {
|
||||||
|
return mockElem.testHtml === '<span>';
|
||||||
|
});
|
||||||
|
|
||||||
|
// First span should be a, should have gotten this value.
|
||||||
|
// This test detects, in particular, WTD-749
|
||||||
|
expect(mockSpans[0].text).toHaveBeenCalledWith('A');
|
||||||
|
expect(mockSpans[1].text).toHaveBeenCalledWith(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("listens for changes in column width", function () {
|
||||||
|
var mockUL = createMockElement("<ul>");
|
||||||
|
mockElement.find.andReturn(mockUL);
|
||||||
|
mockScope.columnWidth = 200;
|
||||||
|
fireWatch("columnWidth", mockScope.columnWidth);
|
||||||
|
expect(mockUL.css).toHaveBeenCalledWith("width", "200px");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates CSS classes", function () {
|
||||||
|
var mockSpans;
|
||||||
|
|
||||||
|
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||||
|
.map(createMockDomainObject);
|
||||||
|
mockScope.values = { a: "a value to find" };
|
||||||
|
mockScope.classes = { a: 'class-a' };
|
||||||
|
|
||||||
|
// Fire an update to the set of values
|
||||||
|
fireWatch("objects");
|
||||||
|
fireWatch("updated");
|
||||||
|
|
||||||
|
// Figure out which span holds the relevant value...
|
||||||
|
mockSpans = mockElements.filter(function (mockElem) {
|
||||||
|
return mockElem.testHtml === '<span>';
|
||||||
|
}).filter(function (mockSpan) {
|
||||||
|
var attrCalls = mockSpan.attr.calls;
|
||||||
|
return attrCalls.some(function (call) {
|
||||||
|
return call.args[0] === 'title' &&
|
||||||
|
call.args[1] === mockScope.values.a;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ...and make sure it also has had its class applied
|
||||||
|
expect(mockSpans[0].addClass)
|
||||||
|
.toHaveBeenCalledWith(mockScope.classes.a);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
341
platform/features/autoflow/test/AutoflowTabularControllerSpec.js
Executable file
341
platform/features/autoflow/test/AutoflowTabularControllerSpec.js
Executable file
@ -0,0 +1,341 @@
|
|||||||
|
|
||||||
|
define(
|
||||||
|
["../src/AutoflowTabularController"],
|
||||||
|
function (AutoflowTabularController) {
|
||||||
|
|
||||||
|
describe("The autoflow tabular controller", function () {
|
||||||
|
var mockScope,
|
||||||
|
mockTimeout,
|
||||||
|
mockSubscriber,
|
||||||
|
mockDomainObject,
|
||||||
|
mockSubscription,
|
||||||
|
controller;
|
||||||
|
|
||||||
|
// Fire watches that are registered as functions.
|
||||||
|
function fireFnWatches() {
|
||||||
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
|
if (typeof call.args[0] === 'function') {
|
||||||
|
call.args[1](call.args[0]());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockScope = jasmine.createSpyObj(
|
||||||
|
"$scope",
|
||||||
|
["$on", "$watch"]
|
||||||
|
);
|
||||||
|
mockTimeout = jasmine.createSpy("$timeout");
|
||||||
|
mockSubscriber = jasmine.createSpyObj(
|
||||||
|
"telemetrySubscriber",
|
||||||
|
["subscribe"]
|
||||||
|
);
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
"domainObject",
|
||||||
|
["getId", "getModel", "getCapability"]
|
||||||
|
);
|
||||||
|
mockSubscription = jasmine.createSpyObj(
|
||||||
|
"subscription",
|
||||||
|
[
|
||||||
|
"unsubscribe",
|
||||||
|
"getTelemetryObjects",
|
||||||
|
"getDomainValue",
|
||||||
|
"getRangeValue"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockSubscriber.subscribe.andReturn(mockSubscription);
|
||||||
|
mockDomainObject.getModel.andReturn({name: "something"});
|
||||||
|
|
||||||
|
controller = new AutoflowTabularController(
|
||||||
|
mockScope,
|
||||||
|
mockTimeout,
|
||||||
|
mockSubscriber
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("listens for the represented domain object", function () {
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"domainObject",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a getter-setter function for filtering", function () {
|
||||||
|
expect(controller.filter()).toEqual("");
|
||||||
|
controller.filter("something");
|
||||||
|
expect(controller.filter()).toEqual("something");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("tracks bounds and adjust number of rows accordingly", function () {
|
||||||
|
// Rows are 15px high, and need room for an 10px slider
|
||||||
|
controller.setBounds({ width: 700, height: 120 });
|
||||||
|
expect(controller.getRows()).toEqual(6); // 110 usable height / 16px
|
||||||
|
controller.setBounds({ width: 700, height: 240 });
|
||||||
|
expect(controller.getRows()).toEqual(14); // 230 usable height / 16px
|
||||||
|
});
|
||||||
|
|
||||||
|
it("subscribes to a represented object's telemetry", function () {
|
||||||
|
// Set up subscription, scope
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
|
// Invoke the watcher with represented domain object
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
|
||||||
|
// Should have subscribed to it
|
||||||
|
expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
|
||||||
|
mockDomainObject,
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should report objects as reported from subscription
|
||||||
|
expect(controller.getTelemetryObjects())
|
||||||
|
.toEqual([mockDomainObject]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("releases subscriptions on destroy", function () {
|
||||||
|
// Set up subscription...
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
|
||||||
|
// Verify precondition
|
||||||
|
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Make sure we're listening for $destroy
|
||||||
|
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||||
|
"$destroy",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fire a destroy event
|
||||||
|
mockScope.$on.mostRecentCall.args[1]();
|
||||||
|
|
||||||
|
// Should have unsubscribed
|
||||||
|
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("presents latest values and latest update state", function () {
|
||||||
|
// Make sure values are available
|
||||||
|
mockSubscription.getDomainValue.andReturn(402654321123);
|
||||||
|
mockSubscription.getRangeValue.andReturn(789);
|
||||||
|
mockDomainObject.getId.andReturn('testId');
|
||||||
|
|
||||||
|
// Set up subscription...
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
|
||||||
|
// Fire subscription callback
|
||||||
|
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||||
|
|
||||||
|
// ...and exposed the results for template to consume
|
||||||
|
expect(controller.updated()).toEqual("1982-278 08:25:21.123Z");
|
||||||
|
expect(controller.rangeValues().testId).toEqual(789);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sorts domain objects by index", function () {
|
||||||
|
var testIndexes = { a: 2, b: 1, c: 3, d: 0 },
|
||||||
|
mockDomainObjects = Object.keys(testIndexes).sort().map(function (id) {
|
||||||
|
var mockDomainObj = jasmine.createSpyObj(
|
||||||
|
"domainObject",
|
||||||
|
["getId", "getModel"]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockDomainObj.getId.andReturn(id);
|
||||||
|
mockDomainObj.getModel.andReturn({ index: testIndexes[id] });
|
||||||
|
|
||||||
|
return mockDomainObj;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expose those domain objects...
|
||||||
|
mockSubscription.getTelemetryObjects.andReturn(mockDomainObjects);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
|
||||||
|
// Fire subscription callback
|
||||||
|
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||||
|
|
||||||
|
// Controller should expose same objects, but sorted by index from model
|
||||||
|
expect(controller.getTelemetryObjects()).toEqual([
|
||||||
|
mockDomainObjects[3], // d, index=0
|
||||||
|
mockDomainObjects[1], // b, index=1
|
||||||
|
mockDomainObjects[0], // a, index=2
|
||||||
|
mockDomainObjects[2] // c, index=3
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses a timeout to throttle update", function () {
|
||||||
|
// Set up subscription...
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
|
// Set the object in view; should not need a timeout
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
expect(mockTimeout.calls.length).toEqual(0);
|
||||||
|
|
||||||
|
// Next call should schedule an update on a timeout
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
expect(mockTimeout.calls.length).toEqual(1);
|
||||||
|
|
||||||
|
// ...but this last one should not, since existing
|
||||||
|
// timeout will cover it
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
expect(mockTimeout.calls.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows changing column width", function () {
|
||||||
|
var initialWidth = controller.columnWidth();
|
||||||
|
controller.increaseColumnWidth();
|
||||||
|
expect(controller.columnWidth()).toBeGreaterThan(initialWidth);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("filter", function () {
|
||||||
|
var doFilter,
|
||||||
|
filteredObjects,
|
||||||
|
filteredObjectNames;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
var telemetryObjects,
|
||||||
|
updateFilteredObjects;
|
||||||
|
|
||||||
|
telemetryObjects = [
|
||||||
|
'DEF123',
|
||||||
|
'abc789',
|
||||||
|
'456abc',
|
||||||
|
'4ab3cdef',
|
||||||
|
'hjs[12].*(){}^\\'
|
||||||
|
].map(function (objectName, index) {
|
||||||
|
var mockTelemetryObject = jasmine.createSpyObj(
|
||||||
|
objectName,
|
||||||
|
["getId", "getModel"]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockTelemetryObject.getId.andReturn(objectName);
|
||||||
|
mockTelemetryObject.getModel.andReturn({
|
||||||
|
name: objectName,
|
||||||
|
index: index
|
||||||
|
});
|
||||||
|
|
||||||
|
return mockTelemetryObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
mockSubscription
|
||||||
|
.getTelemetryObjects
|
||||||
|
.andReturn(telemetryObjects);
|
||||||
|
|
||||||
|
// Trigger domainObject change to create subscription.
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
|
||||||
|
updateFilteredObjects = function () {
|
||||||
|
filteredObjects = controller.getTelemetryObjects();
|
||||||
|
filteredObjectNames = filteredObjects.map(function (o) {
|
||||||
|
return o.getModel().name;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
doFilter = function (term) {
|
||||||
|
controller.filter(term);
|
||||||
|
// Filter is debounced so we have to force it to occur.
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
updateFilteredObjects();
|
||||||
|
};
|
||||||
|
|
||||||
|
updateFilteredObjects();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initially shows all objects", function () {
|
||||||
|
expect(filteredObjectNames).toEqual([
|
||||||
|
'DEF123',
|
||||||
|
'abc789',
|
||||||
|
'456abc',
|
||||||
|
'4ab3cdef',
|
||||||
|
'hjs[12].*(){}^\\'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("by blank string matches all objects", function () {
|
||||||
|
doFilter('');
|
||||||
|
expect(filteredObjectNames).toEqual([
|
||||||
|
'DEF123',
|
||||||
|
'abc789',
|
||||||
|
'456abc',
|
||||||
|
'4ab3cdef',
|
||||||
|
'hjs[12].*(){}^\\'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exactly matches an object name", function () {
|
||||||
|
doFilter('4ab3cdef');
|
||||||
|
expect(filteredObjectNames).toEqual(['4ab3cdef']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("partially matches object names", function () {
|
||||||
|
doFilter('abc');
|
||||||
|
expect(filteredObjectNames).toEqual([
|
||||||
|
'abc789',
|
||||||
|
'456abc'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches case insensitive names", function () {
|
||||||
|
doFilter('def');
|
||||||
|
expect(filteredObjectNames).toEqual([
|
||||||
|
'DEF123',
|
||||||
|
'4ab3cdef'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works as expected with special characters", function () {
|
||||||
|
doFilter('[12]');
|
||||||
|
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||||
|
doFilter('.*');
|
||||||
|
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||||
|
doFilter('.*()');
|
||||||
|
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||||
|
doFilter('.*?');
|
||||||
|
expect(filteredObjectNames).toEqual([]);
|
||||||
|
doFilter('.+');
|
||||||
|
expect(filteredObjectNames).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes CSS classes from limits", function () {
|
||||||
|
var id = mockDomainObject.getId(),
|
||||||
|
testClass = "some-css-class",
|
||||||
|
mockLimitCapability =
|
||||||
|
jasmine.createSpyObj('limit', ['evaluate']);
|
||||||
|
|
||||||
|
mockDomainObject.getCapability.andCallFake(function (key) {
|
||||||
|
return key === 'limit' && mockLimitCapability;
|
||||||
|
});
|
||||||
|
mockLimitCapability.evaluate
|
||||||
|
.andReturn({ cssClass: testClass });
|
||||||
|
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
|
||||||
|
fireFnWatches();
|
||||||
|
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||||
|
|
||||||
|
expect(controller.classes()[id]).toEqual(testClass);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes a counter that changes with each update", function () {
|
||||||
|
var i, prior;
|
||||||
|
|
||||||
|
for (i = 0; i < 10; i += 1) {
|
||||||
|
prior = controller.counter();
|
||||||
|
expect(controller.counter()).toEqual(prior);
|
||||||
|
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||||
|
expect(controller.counter()).not.toEqual(prior);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
39
platform/features/autoflow/test/MCTAutoflowTableSpec.js
Executable file
39
platform/features/autoflow/test/MCTAutoflowTableSpec.js
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
define(
|
||||||
|
["../src/MCTAutoflowTable"],
|
||||||
|
function (MCTAutoflowTable) {
|
||||||
|
|
||||||
|
describe("The mct-autoflow-table directive", function () {
|
||||||
|
var mctAutoflowTable;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mctAutoflowTable = new MCTAutoflowTable();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Real functionality is contained/tested in the linker,
|
||||||
|
// so just check to make sure we're exposing the directive
|
||||||
|
// appropriately.
|
||||||
|
it("is applicable at the element level", function () {
|
||||||
|
expect(mctAutoflowTable.restrict).toEqual("E");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("two-ways binds needed scope variables", function () {
|
||||||
|
expect(mctAutoflowTable.scope).toEqual({
|
||||||
|
objects: "=",
|
||||||
|
values: "=",
|
||||||
|
rows: "=",
|
||||||
|
updated: "=",
|
||||||
|
classes: "=",
|
||||||
|
columnWidth: "=",
|
||||||
|
counter: "="
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a link function", function () {
|
||||||
|
expect(mctAutoflowTable.link).toEqual(jasmine.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -45,7 +45,7 @@ define(
|
|||||||
|
|
||||||
FollowIndicator.prototype.getText = function () {
|
FollowIndicator.prototype.getText = function () {
|
||||||
var timer = this.timerService.getTimer();
|
var timer = this.timerService.getTimer();
|
||||||
return timer ? ('Following timer ' + timer.name) : NO_TIMER;
|
return (timer) ? 'Following timer ' + timer.getModel().name : NO_TIMER;
|
||||||
};
|
};
|
||||||
|
|
||||||
FollowIndicator.prototype.getDescription = function () {
|
FollowIndicator.prototype.getDescription = function () {
|
||||||
|
@ -42,15 +42,18 @@ define(["../../src/indicators/FollowIndicator"], function (FollowIndicator) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when a timer is set", function () {
|
describe("when a timer is set", function () {
|
||||||
var testObject;
|
var testModel;
|
||||||
|
var mockDomainObject;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testObject = { name: "some timer!" };
|
testModel = { name: "some timer!" };
|
||||||
mockTimerService.getTimer.andReturn(testObject);
|
mockDomainObject = jasmine.createSpyObj('timer', ['getModel']);
|
||||||
|
mockDomainObject.getModel.andReturn(testModel);
|
||||||
|
mockTimerService.getTimer.andReturn(mockDomainObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays the timer's name", function () {
|
it("displays the timer's name", function () {
|
||||||
expect(indicator.getText().indexOf(testObject.name))
|
expect(indicator.getText().indexOf(testModel.name))
|
||||||
.not.toEqual(-1);
|
.not.toEqual(-1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -255,8 +255,6 @@ define(
|
|||||||
if (this.nextDatum) {
|
if (this.nextDatum) {
|
||||||
this.updateValues(this.nextDatum);
|
this.updateValues(this.nextDatum);
|
||||||
delete this.nextDatum;
|
delete this.nextDatum;
|
||||||
} else {
|
|
||||||
this.updateValues(this.$scope.imageHistory[this.$scope.imageHistory.length - 1]);
|
|
||||||
}
|
}
|
||||||
this.autoScroll = true;
|
this.autoScroll = true;
|
||||||
}
|
}
|
||||||
|
@ -183,17 +183,6 @@ define(
|
|||||||
expect(controller.getImageUrl()).toEqual(newUrl);
|
expect(controller.getImageUrl()).toEqual(newUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("forwards large image view to latest image in history on un-pause", function () {
|
|
||||||
$scope.imageHistory = [
|
|
||||||
{ utc: 1434600258122, url: 'some/url1', selected: false},
|
|
||||||
{ utc: 1434600258123, url: 'some/url2', selected: false}
|
|
||||||
];
|
|
||||||
controller.paused(true);
|
|
||||||
controller.paused(false);
|
|
||||||
|
|
||||||
expect(controller.getImageUrl()).toEqual(controller.getImageUrl($scope.imageHistory[1]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("subscribes to telemetry", function () {
|
it("subscribes to telemetry", function () {
|
||||||
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
|
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
|
||||||
newDomainObject,
|
newDomainObject,
|
||||||
@ -238,7 +227,7 @@ define(
|
|||||||
expect(controller.updateHistory(mockDatum)).toBe(false);
|
expect(controller.updateHistory(mockDatum)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when user clicks on imagery thumbnail", function () {
|
describe("user clicks on imagery thumbnail", function () {
|
||||||
var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false};
|
var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false};
|
||||||
|
|
||||||
it("pauses and adds selected class to imagery thumbnail", function () {
|
it("pauses and adds selected class to imagery thumbnail", function () {
|
||||||
@ -259,7 +248,6 @@ define(
|
|||||||
expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc));
|
expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("initially shows an empty string for date/time", function () {
|
it("initially shows an empty string for date/time", function () {
|
||||||
|
@ -260,9 +260,7 @@ define([
|
|||||||
"key": "LayoutController",
|
"key": "LayoutController",
|
||||||
"implementation": LayoutController,
|
"implementation": LayoutController,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$scope",
|
"$scope"
|
||||||
"$element",
|
|
||||||
"openmct"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -272,8 +270,7 @@ define([
|
|||||||
"$scope",
|
"$scope",
|
||||||
"$q",
|
"$q",
|
||||||
"dialogService",
|
"dialogService",
|
||||||
"openmct",
|
"openmct"
|
||||||
"$element"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
ng-controller="FixedController as controller">
|
ng-controller="FixedController as controller">
|
||||||
|
|
||||||
<!-- Background grid -->
|
<!-- Background grid -->
|
||||||
<div class="l-grid-holder" ng-click="controller.bypassSelection($event)">
|
<div class="l-grid-holder" ng-click="controller.clearSelection()">
|
||||||
<div class="l-grid l-grid-x"
|
<div class="l-grid l-grid-x"
|
||||||
ng-if="!controller.getGridSize()[0] < 3"
|
ng-if="!controller.getGridSize()[0] < 3"
|
||||||
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
||||||
@ -35,28 +35,33 @@
|
|||||||
<!-- Fixed position elements -->
|
<!-- Fixed position elements -->
|
||||||
<div ng-repeat="element in controller.getElements()"
|
<div ng-repeat="element in controller.getElements()"
|
||||||
class="l-fixed-position-item s-selectable s-moveable s-hover-border"
|
class="l-fixed-position-item s-selectable s-moveable s-hover-border"
|
||||||
|
ng-class="{
|
||||||
|
's-not-selected': controller.selected() && !controller.selected(element),
|
||||||
|
's-selected': controller.selected(element)
|
||||||
|
}"
|
||||||
ng-style="element.style"
|
ng-style="element.style"
|
||||||
mct-selectable="controller.getContext(element)"
|
ng-click="controller.select(element)">
|
||||||
mct-init-select="controller.shouldSelect(element)">
|
|
||||||
<mct-include key="element.template"
|
<mct-include key="element.template"
|
||||||
parameters="{ gridSize: controller.getGridSize() }"
|
parameters="{ gridSize: controller.getGridSize() }"
|
||||||
ng-model="element">
|
ng-model="element">
|
||||||
</mct-include>
|
</mct-include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Selection highlight, handles -->
|
<!-- Selection highlight, handles -->
|
||||||
<span class="s-selected s-moveable" ng-if="controller.isElementSelected()">
|
<span class="s-selected s-moveable" ng-if="controller.selected()">
|
||||||
<div class="l-fixed-position-item t-edit-handle-holder"
|
<div class="l-fixed-position-item t-edit-handle-holder"
|
||||||
mct-drag-down="controller.moveHandle().startDrag()"
|
mct-drag-down="controller.moveHandle().startDrag(controller.selected())"
|
||||||
mct-drag="controller.moveHandle().continueDrag(delta)"
|
mct-drag="controller.moveHandle().continueDrag(delta)"
|
||||||
mct-drag-up="controller.endDrag()"
|
mct-drag-up="controller.moveHandle().endDrag()"
|
||||||
ng-style="controller.getSelectedElementStyle()">
|
ng-style="controller.selected().style">
|
||||||
</div>
|
</div>
|
||||||
<div ng-repeat="handle in controller.handles()"
|
<div ng-repeat="handle in controller.handles()"
|
||||||
class="l-fixed-position-item-handle edit-corner"
|
class="l-fixed-position-item-handle edit-corner"
|
||||||
ng-style="handle.style()"
|
ng-style="handle.style()"
|
||||||
mct-drag-down="handle.startDrag()"
|
mct-drag-down="handle.startDrag()"
|
||||||
mct-drag="handle.continueDrag(delta)"
|
mct-drag="handle.continueDrag(delta)"
|
||||||
mct-drag-up="controller.endDrag(handle)">
|
mct-drag-up="handle.endDrag()">
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class="frame frame-template t-frame-inner abs t-object-type-{{ domainObject.getModel().type }}">
|
<div class="frame frame-template t-frame-inner abs t-object-type-{{ representation.selected.key }}">
|
||||||
<div class="abs object-browse-bar l-flex-row">
|
<div class="abs object-browse-bar l-flex-row">
|
||||||
<div class="left flex-elem l-flex-row grows">
|
<div class="left flex-elem l-flex-row grows">
|
||||||
<mct-representation
|
<mct-representation
|
||||||
|
@ -22,12 +22,10 @@
|
|||||||
|
|
||||||
<div class="abs l-layout"
|
<div class="abs l-layout"
|
||||||
ng-controller="LayoutController as controller"
|
ng-controller="LayoutController as controller"
|
||||||
ng-click="controller.bypassSelection($event)">
|
ng-click="controller.clearSelection()">
|
||||||
|
|
||||||
<!-- Background grid -->
|
<!-- Background grid -->
|
||||||
<div class="l-grid-holder"
|
<div class="l-grid-holder" ng-click="controller.clearSelection()">
|
||||||
ng-show="!controller.drilledIn"
|
|
||||||
ng-click="controller.bypassSelection($event)">
|
|
||||||
<div class="l-grid l-grid-x"
|
<div class="l-grid l-grid-x"
|
||||||
ng-if="!controller.getGridSize()[0] < 3"
|
ng-if="!controller.getGridSize()[0] < 3"
|
||||||
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
||||||
@ -36,13 +34,10 @@
|
|||||||
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
|
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}"
|
<div class='abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border'
|
||||||
data-layout-id="{{childObject.getId() + '-' + $id}}"
|
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-selected':controller.selected(childObject) }"
|
||||||
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
|
|
||||||
ng-repeat="childObject in composition"
|
ng-repeat="childObject in composition"
|
||||||
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
|
ng-click="controller.select($event, childObject.getId())"
|
||||||
mct-selectable="controller.getContext(childObject, true)"
|
|
||||||
ng-dblclick="controller.drill($event, childObject)"
|
|
||||||
ng-style="controller.getFrameStyle(childObject.getId())">
|
ng-style="controller.getFrameStyle(childObject.getId())">
|
||||||
|
|
||||||
<mct-representation key="'frame'"
|
<mct-representation key="'frame'"
|
||||||
@ -50,7 +45,7 @@
|
|||||||
mct-object="childObject">
|
mct-object="childObject">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
<!-- Drag handles -->
|
<!-- Drag handles -->
|
||||||
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
|
<span class="abs t-edit-handle-holder s-hover-border" ng-if="controller.selected(childObject)">
|
||||||
<span class="edit-handle edit-move"
|
<span class="edit-handle edit-move"
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
|
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
|
||||||
mct-drag="controller.continueDrag(delta)"
|
mct-drag="controller.continueDrag(delta)"
|
||||||
@ -78,6 +73,7 @@
|
|||||||
mct-drag-up="controller.endDrag()">
|
mct-drag-up="controller.endDrag()">
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,7 +47,7 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @param {Scope} $scope the controller's Angular scope
|
* @param {Scope} $scope the controller's Angular scope
|
||||||
*/
|
*/
|
||||||
function FixedController($scope, $q, dialogService, openmct, $element) {
|
function FixedController($scope, $q, dialogService, openmct) {
|
||||||
this.names = {}; // Cache names by ID
|
this.names = {}; // Cache names by ID
|
||||||
this.values = {}; // Cache values by ID
|
this.values = {}; // Cache values by ID
|
||||||
this.elementProxiesById = {};
|
this.elementProxiesById = {};
|
||||||
@ -55,11 +55,9 @@ define(
|
|||||||
this.telemetryObjects = [];
|
this.telemetryObjects = [];
|
||||||
this.subscriptions = [];
|
this.subscriptions = [];
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.$element = $element;
|
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
|
|
||||||
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
|
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
|
||||||
this.fixedViewSelectable = false;
|
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
[
|
[
|
||||||
@ -89,8 +87,9 @@ define(
|
|||||||
|
|
||||||
// Update the style for a selected element
|
// Update the style for a selected element
|
||||||
function updateSelectionStyle() {
|
function updateSelectionStyle() {
|
||||||
if (self.selectedElementProxy) {
|
var element = self.selection && self.selection.get();
|
||||||
self.selectedElementProxy.style = convertPosition(self.selectedElementProxy);
|
if (element) {
|
||||||
|
element.style = convertPosition(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,19 +136,25 @@ define(
|
|||||||
|
|
||||||
// Decorate elements in the current configuration
|
// Decorate elements in the current configuration
|
||||||
function refreshElements() {
|
function refreshElements() {
|
||||||
var elements = (($scope.configuration || {}).elements || []);
|
// Cache selection; we are instantiating new proxies
|
||||||
|
// so we may want to restore this.
|
||||||
|
var selected = self.selection && self.selection.get(),
|
||||||
|
elements = (($scope.configuration || {}).elements || []),
|
||||||
|
index = -1; // Start with a 'not-found' value
|
||||||
|
|
||||||
|
// Find the selection in the new array
|
||||||
|
if (selected !== undefined) {
|
||||||
|
index = elements.indexOf(selected.element);
|
||||||
|
}
|
||||||
|
|
||||||
// Create the new proxies...
|
// Create the new proxies...
|
||||||
self.elementProxies = elements.map(makeProxyElement);
|
self.elementProxies = elements.map(makeProxyElement);
|
||||||
|
|
||||||
// If selection is not in array, select parent.
|
// Clear old selection, and restore if appropriate
|
||||||
// Otherwise, set the element to select after refresh.
|
if (self.selection) {
|
||||||
if (self.selectedElementProxy) {
|
self.selection.deselect();
|
||||||
var index = elements.indexOf(self.selectedElementProxy.element);
|
if (index > -1) {
|
||||||
if (index === -1) {
|
self.select(self.elementProxies[index]);
|
||||||
self.$element[0].click();
|
|
||||||
} else if (!self.elementToSelectAfterRefresh) {
|
|
||||||
self.elementToSelectAfterRefresh = self.elementProxies[index].element;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,12 +224,12 @@ define(
|
|||||||
$scope.configuration.elements || [];
|
$scope.configuration.elements || [];
|
||||||
// Store the position of this element.
|
// Store the position of this element.
|
||||||
$scope.configuration.elements.push(element);
|
$scope.configuration.elements.push(element);
|
||||||
|
|
||||||
self.elementToSelectAfterRefresh = element;
|
|
||||||
|
|
||||||
// Refresh displayed elements
|
// Refresh displayed elements
|
||||||
refreshElements();
|
refreshElements();
|
||||||
|
// Select the newly-added element
|
||||||
|
self.select(
|
||||||
|
self.elementProxies[self.elementProxies.length - 1]
|
||||||
|
);
|
||||||
// Mark change as persistable
|
// Mark change as persistable
|
||||||
if ($scope.commit) {
|
if ($scope.commit) {
|
||||||
$scope.commit("Dropped an element.");
|
$scope.commit("Dropped an element.");
|
||||||
@ -258,36 +263,21 @@ define(
|
|||||||
self.getTelemetry($scope.domainObject);
|
self.getTelemetry($scope.domainObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the selectable object in response to the selection change event.
|
|
||||||
function setSelection(selectable) {
|
|
||||||
var selection = selectable[0];
|
|
||||||
|
|
||||||
if (!selection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selection.context.elementProxy) {
|
|
||||||
self.selectedElementProxy = selection.context.elementProxy;
|
|
||||||
self.mvHandle = self.generateDragHandle(self.selectedElementProxy);
|
|
||||||
self.resizeHandles = self.generateDragHandles(self.selectedElementProxy);
|
|
||||||
} else {
|
|
||||||
// Make fixed view selectable if it's not already.
|
|
||||||
if (!self.fixedViewSelectable && selectable.length === 1) {
|
|
||||||
self.fixedViewSelectable = true;
|
|
||||||
selection.context.viewProxy = new FixedProxy(addElement, $q, dialogService);
|
|
||||||
self.openmct.selection.select(selection);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.resizeHandles = [];
|
|
||||||
self.mvHandle = undefined;
|
|
||||||
self.selectedElementProxy = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.elementProxies = [];
|
this.elementProxies = [];
|
||||||
this.generateDragHandle = generateDragHandle;
|
this.generateDragHandle = generateDragHandle;
|
||||||
this.generateDragHandles = generateDragHandles;
|
this.generateDragHandles = generateDragHandles;
|
||||||
this.updateSelectionStyle = updateSelectionStyle;
|
|
||||||
|
// Track current selection state
|
||||||
|
$scope.$watch("selection", function (selection) {
|
||||||
|
this.selection = selection;
|
||||||
|
|
||||||
|
// Expose the view's selection proxy
|
||||||
|
if (this.selection) {
|
||||||
|
this.selection.proxy(
|
||||||
|
new FixedProxy(addElement, $q, dialogService)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
// Detect changes to grid size
|
// Detect changes to grid size
|
||||||
$scope.$watch("model.layoutGrid", updateElementPositions);
|
$scope.$watch("model.layoutGrid", updateElementPositions);
|
||||||
@ -308,13 +298,10 @@ define(
|
|||||||
$scope.$on("$destroy", function () {
|
$scope.$on("$destroy", function () {
|
||||||
self.unsubscribe();
|
self.unsubscribe();
|
||||||
self.openmct.time.off("bounds", updateDisplayBounds);
|
self.openmct.time.off("bounds", updateDisplayBounds);
|
||||||
self.openmct.selection.off("change", setSelection);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Respond to external bounds changes
|
// Respond to external bounds changes
|
||||||
this.openmct.time.on("bounds", updateDisplayBounds);
|
this.openmct.time.on("bounds", updateDisplayBounds);
|
||||||
this.openmct.selection.on('change', setSelection);
|
|
||||||
this.$element.on('click', this.bypassSelection.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -373,47 +360,22 @@ define(
|
|||||||
*/
|
*/
|
||||||
FixedController.prototype.updateView = function (telemetryObject, datum) {
|
FixedController.prototype.updateView = function (telemetryObject, datum) {
|
||||||
var metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
var metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||||
var telemetryKeyToDisplay = this.chooseTelemetryKeyToDisplay(metadata);
|
var rangeMetadata = metadata.valuesForHints(['range'])[0];
|
||||||
var formattedTelemetryValue = this.getFormattedTelemetryValueForKey(telemetryKeyToDisplay, datum, metadata);
|
var rangeKey = rangeMetadata.source || rangeMetadata.key;
|
||||||
|
var valueMetadata = metadata.value(rangeKey);
|
||||||
var limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
var limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||||
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, telemetryKeyToDisplay);
|
var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
|
||||||
|
var value = datum[valueMetadata.key];
|
||||||
|
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, rangeKey);
|
||||||
|
|
||||||
this.setDisplayedValue(
|
this.setDisplayedValue(
|
||||||
telemetryObject,
|
telemetryObject,
|
||||||
formattedTelemetryValue,
|
formatter.format(value),
|
||||||
alarm && alarm.cssClass
|
alarm && alarm.cssClass
|
||||||
);
|
);
|
||||||
this.digest();
|
this.digest();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
FixedController.prototype.getFormattedTelemetryValueForKey = function (telemetryKeyToDisplay, datum, metadata) {
|
|
||||||
var valueMetadata = metadata.value(telemetryKeyToDisplay);
|
|
||||||
var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
|
|
||||||
|
|
||||||
return formatter.format(datum[valueMetadata.key]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
FixedController.prototype.chooseTelemetryKeyToDisplay = function (metadata) {
|
|
||||||
// If there is a range value, show that preferentially
|
|
||||||
var telemetryKeyToDisplay = metadata.valuesForHints(['range'])[0];
|
|
||||||
|
|
||||||
// If no range is defined, default to the highest priority non time-domain data.
|
|
||||||
if (telemetryKeyToDisplay === undefined) {
|
|
||||||
var valuesOrderedByPriority = metadata.values();
|
|
||||||
telemetryKeyToDisplay = valuesOrderedByPriority.filter(function (valueMetadata) {
|
|
||||||
return !(valueMetadata.hints.domain);
|
|
||||||
})[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return telemetryKeyToDisplay.source;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the last historical data point for the given domain objects
|
* Request the last historical data point for the given domain objects
|
||||||
* @param {object[]} objects
|
* @param {object[]} objects
|
||||||
@ -426,9 +388,7 @@ define(
|
|||||||
objects.forEach(function (object) {
|
objects.forEach(function (object) {
|
||||||
self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
|
self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
if (data.length > 0) {
|
|
||||||
self.updateView(object, data[data.length - 1]);
|
self.updateView(object, data[data.length - 1]);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return objects;
|
return objects;
|
||||||
@ -505,56 +465,38 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the element should be selected or not.
|
* Check if the element is currently selected, or (if no
|
||||||
*
|
* argument is supplied) get the currently selected element.
|
||||||
* @param elementProxy the element to check
|
* @returns {boolean} true if selected
|
||||||
* @returns {boolean} true if the element should be selected.
|
|
||||||
*/
|
*/
|
||||||
FixedController.prototype.shouldSelect = function (elementProxy) {
|
FixedController.prototype.selected = function (element) {
|
||||||
if (elementProxy.element === this.elementToSelectAfterRefresh) {
|
var selection = this.selection;
|
||||||
delete this.elementToSelectAfterRefresh;
|
return selection && ((arguments.length > 0) ?
|
||||||
return true;
|
selection.selected(element) : selection.get());
|
||||||
} else {
|
};
|
||||||
return false;
|
|
||||||
|
/**
|
||||||
|
* Set the active user selection in this view.
|
||||||
|
* @param element the element to select
|
||||||
|
*/
|
||||||
|
FixedController.prototype.select = function select(element) {
|
||||||
|
if (this.selection) {
|
||||||
|
// Update selection...
|
||||||
|
this.selection.select(element);
|
||||||
|
// ...as well as move, resize handles
|
||||||
|
this.mvHandle = this.generateDragHandle(element);
|
||||||
|
this.resizeHandles = this.generateDragHandles(element);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an element is currently selected.
|
* Clear the current user selection.
|
||||||
*
|
|
||||||
* @returns {boolean} true if an element is selected.
|
|
||||||
*/
|
*/
|
||||||
FixedController.prototype.isElementSelected = function () {
|
FixedController.prototype.clearSelection = function () {
|
||||||
return (this.selectedElementProxy) ? true : false;
|
if (this.selection) {
|
||||||
};
|
this.selection.deselect();
|
||||||
|
this.resizeHandles = [];
|
||||||
/**
|
this.mvHandle = undefined;
|
||||||
* Gets the style for the selected element.
|
|
||||||
*
|
|
||||||
* @returns {string} element style
|
|
||||||
*/
|
|
||||||
FixedController.prototype.getSelectedElementStyle = function () {
|
|
||||||
return (this.selectedElementProxy) ? this.selectedElementProxy.style : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the selected element.
|
|
||||||
*
|
|
||||||
* @returns the selected element
|
|
||||||
*/
|
|
||||||
FixedController.prototype.getSelectedElement = function () {
|
|
||||||
return this.selectedElementProxy;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prevents the event from bubbling up if drag is in progress.
|
|
||||||
*/
|
|
||||||
FixedController.prototype.bypassSelection = function ($event) {
|
|
||||||
if (this.dragInProgress) {
|
|
||||||
if ($event) {
|
|
||||||
$event.stopPropagation();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -575,38 +517,6 @@ define(
|
|||||||
return this.mvHandle;
|
return this.mvHandle;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the selection context.
|
|
||||||
*
|
|
||||||
* @param elementProxy the element proxy
|
|
||||||
* @returns {object} the context object which includes elementProxy and toolbar
|
|
||||||
*/
|
|
||||||
FixedController.prototype.getContext = function (elementProxy) {
|
|
||||||
return {
|
|
||||||
elementProxy: elementProxy,
|
|
||||||
toolbar: elementProxy
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End drag.
|
|
||||||
*
|
|
||||||
* @param handle the resize handle
|
|
||||||
*/
|
|
||||||
FixedController.prototype.endDrag = function (handle) {
|
|
||||||
this.dragInProgress = true;
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
this.dragInProgress = false;
|
|
||||||
}.bind(this), 0);
|
|
||||||
|
|
||||||
if (handle) {
|
|
||||||
handle.endDrag();
|
|
||||||
} else {
|
|
||||||
this.moveHandle().endDrag();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return FixedController;
|
return FixedController;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -65,7 +65,7 @@ define(
|
|||||||
* Start a drag gesture. This should be called when a drag
|
* Start a drag gesture. This should be called when a drag
|
||||||
* begins to track initial state.
|
* begins to track initial state.
|
||||||
*/
|
*/
|
||||||
FixedDragHandle.prototype.startDrag = function () {
|
FixedDragHandle.prototype.startDrag = function startDrag() {
|
||||||
// Cache initial x/y positions
|
// Cache initial x/y positions
|
||||||
this.dragging = {
|
this.dragging = {
|
||||||
x: this.elementHandle.x(),
|
x: this.elementHandle.x(),
|
||||||
|
@ -27,11 +27,9 @@
|
|||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'zepto',
|
|
||||||
'./LayoutDrag'
|
'./LayoutDrag'
|
||||||
],
|
],
|
||||||
function (
|
function (
|
||||||
$,
|
|
||||||
LayoutDrag
|
LayoutDrag
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -52,12 +50,10 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @param {Scope} $scope the controller's Angular scope
|
* @param {Scope} $scope the controller's Angular scope
|
||||||
*/
|
*/
|
||||||
function LayoutController($scope, $element, openmct) {
|
function LayoutController($scope) {
|
||||||
var self = this,
|
var self = this,
|
||||||
callbackCount = 0;
|
callbackCount = 0;
|
||||||
|
|
||||||
this.$element = $element;
|
|
||||||
|
|
||||||
// Update grid size when it changed
|
// Update grid size when it changed
|
||||||
function updateGridSize(layoutGrid) {
|
function updateGridSize(layoutGrid) {
|
||||||
var oldSize = self.gridSize;
|
var oldSize = self.gridSize;
|
||||||
@ -127,11 +123,12 @@ define(
|
|||||||
self.layoutPanels(ids);
|
self.layoutPanels(ids);
|
||||||
self.setFrames(ids);
|
self.setFrames(ids);
|
||||||
|
|
||||||
if (self.selectedId &&
|
// If there is a newly-dropped object, select it.
|
||||||
self.selectedId !== $scope.domainObject.getId() &&
|
if (self.droppedIdToSelectAfterRefresh) {
|
||||||
composition.indexOf(self.selectedId) === -1) {
|
self.select(null, self.droppedIdToSelectAfterRefresh);
|
||||||
// Click triggers selection of layout parent.
|
delete self.droppedIdToSelectAfterRefresh;
|
||||||
self.$element[0].click();
|
} else if (composition.indexOf(self.selectedId) === -1) {
|
||||||
|
self.clearSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -163,39 +160,22 @@ define(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sets the selectable object in response to the selection change event.
|
|
||||||
function setSelection(selectable) {
|
|
||||||
var selection = selectable[0];
|
|
||||||
|
|
||||||
if (!selection) {
|
|
||||||
delete self.selectedId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.selectedId = selection.context.oldItem.getId();
|
|
||||||
self.drilledIn = undefined;
|
|
||||||
self.selectable = selectable;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.positions = {};
|
this.positions = {};
|
||||||
this.rawPositions = {};
|
this.rawPositions = {};
|
||||||
this.gridSize = DEFAULT_GRID_SIZE;
|
this.gridSize = DEFAULT_GRID_SIZE;
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
this.drilledIn = undefined;
|
|
||||||
this.openmct = openmct;
|
|
||||||
|
|
||||||
// Watch for changes to the grid size in the model
|
// Watch for changes to the grid size in the model
|
||||||
$scope.$watch("model.layoutGrid", updateGridSize);
|
$scope.$watch("model.layoutGrid", updateGridSize);
|
||||||
|
|
||||||
|
$scope.$watch("selection", function (selection) {
|
||||||
|
this.selection = selection;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
// Update composed objects on screen, and position panes
|
// Update composed objects on screen, and position panes
|
||||||
$scope.$watchCollection("model.composition", refreshComposition);
|
$scope.$watchCollection("model.composition", refreshComposition);
|
||||||
|
|
||||||
openmct.selection.on('change', setSelection);
|
// Position panes where they are dropped
|
||||||
|
|
||||||
$scope.$on("$destroy", function () {
|
|
||||||
openmct.selection.off("change", setSelection);
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on("mctDrop", handleDrop);
|
$scope.$on("mctDrop", handleDrop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,14 +357,37 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the object is currently selected.
|
* Check if the object is currently selected.
|
||||||
*
|
*
|
||||||
* @param {string} obj the object to check for selection
|
* @param {string} obj the object to check for selection
|
||||||
* @returns {boolean} true if selected, otherwise false
|
* @returns {boolean} true if selected, otherwise false
|
||||||
*/
|
*/
|
||||||
LayoutController.prototype.selected = function (obj) {
|
LayoutController.prototype.selected = function (obj) {
|
||||||
var sobj = this.openmct.selection.get()[0];
|
return !!this.selectedId && this.selectedId === obj.getId();
|
||||||
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active user selection in this view.
|
||||||
|
*
|
||||||
|
* @param event the mouse event
|
||||||
|
* @param {string} id the object id
|
||||||
|
*/
|
||||||
|
LayoutController.prototype.select = function (event, id) {
|
||||||
|
if (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (this.selection) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedId = id;
|
||||||
|
|
||||||
|
var selectedObj = {};
|
||||||
|
selectedObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id);
|
||||||
|
|
||||||
|
if (this.selection) {
|
||||||
|
this.selection.select(selectedObj);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -393,7 +396,7 @@ define(
|
|||||||
* @param {string} id the object id
|
* @param {string} id the object id
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
LayoutController.prototype.toggleFrame = function (id, domainObject) {
|
LayoutController.prototype.toggleFrame = function (id) {
|
||||||
var configuration = this.$scope.configuration;
|
var configuration = this.$scope.configuration;
|
||||||
|
|
||||||
if (!configuration.panels[id]) {
|
if (!configuration.panels[id]) {
|
||||||
@ -401,75 +404,21 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
|
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
|
||||||
|
this.select(undefined, id); // reselect so toolbar updates
|
||||||
var selection = this.openmct.selection.get();
|
|
||||||
selection[0].context.toolbar = this.getToolbar(id, domainObject);
|
|
||||||
this.openmct.selection.select(selection); // reselect so toolbar updates
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the toolbar object for the given domain object.
|
* Clear the current user selection.
|
||||||
*
|
|
||||||
* @param id the domain object id
|
|
||||||
* @param domainObject the domain object
|
|
||||||
* @returns {object}
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
LayoutController.prototype.getToolbar = function (id, domainObject) {
|
LayoutController.prototype.clearSelection = function () {
|
||||||
var toolbarObj = {};
|
|
||||||
toolbarObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id, domainObject);
|
|
||||||
return toolbarObj;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bypasses selection if drag is in progress.
|
|
||||||
*
|
|
||||||
* @param event the angular event object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.bypassSelection = function (event) {
|
|
||||||
if (this.dragInProgress) {
|
if (this.dragInProgress) {
|
||||||
if (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the domain object is drilled in.
|
|
||||||
*
|
|
||||||
* @param domainObject the domain object
|
|
||||||
* @return true if the object is drilled in, false otherwise
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.isDrilledIn = function (domainObject) {
|
|
||||||
return this.drilledIn === domainObject.getId();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts the given object in the drilled-in mode.
|
|
||||||
*
|
|
||||||
* @param event the angular event object
|
|
||||||
* @param domainObject the domain object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.drill = function (event, domainObject) {
|
|
||||||
if (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!domainObject.getCapability('editor').inEditContext()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!domainObject.hasCapability('composition')) {
|
if (this.selection) {
|
||||||
return;
|
this.selection.deselect();
|
||||||
|
delete this.selectedId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable since fixed position doesn't use the selection API yet
|
|
||||||
if (domainObject.getModel().type === 'telemetry.fixed') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.drilledIn = domainObject.getId();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -491,36 +440,6 @@ define(
|
|||||||
return this.gridSize;
|
return this.gridSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the selection context.
|
|
||||||
*
|
|
||||||
* @param domainObject the domain object
|
|
||||||
* @returns {object} the context object which includes
|
|
||||||
* item, oldItem and toolbar
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.getContext = function (domainObject, toolbar) {
|
|
||||||
return {
|
|
||||||
item: domainObject.useCapability('adapter'),
|
|
||||||
oldItem: domainObject,
|
|
||||||
toolbar: toolbar ? this.getToolbar(domainObject.getId(), domainObject) : undefined
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects a newly-dropped object.
|
|
||||||
*
|
|
||||||
* @param classSelector the css class selector
|
|
||||||
* @param domainObject the domain object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.selectIfNew = function (selector, domainObject) {
|
|
||||||
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
|
|
||||||
setTimeout(function () {
|
|
||||||
$('[data-layout-id="' + selector + '"]')[0].click();
|
|
||||||
delete this.droppedIdToSelectAfterRefresh;
|
|
||||||
}.bind(this), 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return LayoutController;
|
return LayoutController;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -55,8 +55,8 @@ define(
|
|||||||
* @param element the fixed position element, as stored in its
|
* @param element the fixed position element, as stored in its
|
||||||
* configuration
|
* configuration
|
||||||
* @param index the element's index within its array
|
* @param index the element's index within its array
|
||||||
* @param {Array} elements the full array of elements
|
|
||||||
* @param {number[]} gridSize the current layout grid size in [x,y] from
|
* @param {number[]} gridSize the current layout grid size in [x,y] from
|
||||||
|
* @param {Array} elements the full array of elements
|
||||||
*/
|
*/
|
||||||
function ElementProxy(element, index, elements, gridSize) {
|
function ElementProxy(element, index, elements, gridSize) {
|
||||||
/**
|
/**
|
||||||
|
@ -21,14 +21,8 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
["../src/FixedController"],
|
||||||
"../src/FixedController",
|
function (FixedController) {
|
||||||
"zepto"
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
FixedController,
|
|
||||||
$
|
|
||||||
) {
|
|
||||||
|
|
||||||
describe("The Fixed Position controller", function () {
|
describe("The Fixed Position controller", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
@ -52,9 +46,6 @@ define(
|
|||||||
mockMetadata,
|
mockMetadata,
|
||||||
mockTimeSystem,
|
mockTimeSystem,
|
||||||
mockLimitEvaluator,
|
mockLimitEvaluator,
|
||||||
mockSelection,
|
|
||||||
$element = [],
|
|
||||||
selectable = [],
|
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
// Utility function; find a watch for a given expression
|
// Utility function; find a watch for a given expression
|
||||||
@ -187,36 +178,23 @@ define(
|
|||||||
Promise.resolve(mockChildren)
|
Promise.resolve(mockChildren)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
mockScope.model = testModel;
|
mockScope.model = testModel;
|
||||||
mockScope.configuration = testConfiguration;
|
mockScope.configuration = testConfiguration;
|
||||||
|
mockScope.selection = jasmine.createSpyObj(
|
||||||
selectable[0] = {
|
'selection',
|
||||||
context: {
|
['select', 'get', 'selected', 'deselect', 'proxy']
|
||||||
oldItem: mockDomainObject
|
);
|
||||||
}
|
|
||||||
};
|
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
|
||||||
'select',
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.andCallThrough();
|
|
||||||
|
|
||||||
mockOpenMCT = {
|
mockOpenMCT = {
|
||||||
time: mockConductor,
|
time: mockConductor,
|
||||||
telemetry: mockTelemetryAPI,
|
telemetry: mockTelemetryAPI,
|
||||||
composition: mockCompositionAPI,
|
composition: mockCompositionAPI
|
||||||
selection: mockSelection
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$element = $('<div></div>');
|
|
||||||
spyOn($element[0], 'click');
|
|
||||||
|
|
||||||
mockMetadata = jasmine.createSpyObj('mockMetadata', [
|
mockMetadata = jasmine.createSpyObj('mockMetadata', [
|
||||||
'valuesForHints',
|
'valuesForHints',
|
||||||
'value',
|
'value'
|
||||||
'values'
|
|
||||||
]);
|
]);
|
||||||
mockMetadata.value.andReturn({
|
mockMetadata.value.andReturn({
|
||||||
key: 'value'
|
key: 'value'
|
||||||
@ -248,11 +226,11 @@ define(
|
|||||||
mockScope,
|
mockScope,
|
||||||
mockQ,
|
mockQ,
|
||||||
mockDialogService,
|
mockDialogService,
|
||||||
mockOpenMCT,
|
mockOpenMCT
|
||||||
$element
|
|
||||||
);
|
);
|
||||||
|
|
||||||
findWatch("model.layoutGrid")(testModel.layoutGrid);
|
findWatch("model.layoutGrid")(testModel.layoutGrid);
|
||||||
|
findWatch("selection")(mockScope.selection);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("subscribes when a domain object is available", function () {
|
it("subscribes when a domain object is available", function () {
|
||||||
@ -328,41 +306,41 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows elements to be selected", function () {
|
it("allows elements to be selected", function () {
|
||||||
testModel.modified = 1;
|
|
||||||
findWatch("model.modified")(testModel.modified);
|
|
||||||
|
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
expect(controller.isElementSelected()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows selection retrieval", function () {
|
|
||||||
var elements;
|
var elements;
|
||||||
|
|
||||||
testModel.modified = 1;
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
|
||||||
elements = controller.getElements();
|
elements = controller.getElements();
|
||||||
selectable[0].context.elementProxy = elements[1];
|
controller.select(elements[1]);
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
expect(mockScope.selection.select)
|
||||||
|
.toHaveBeenCalledWith(elements[1]);
|
||||||
expect(controller.getSelectedElement()).toEqual(elements[1]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("selects the parent view when selected element is removed", function () {
|
it("allows selection retrieval", function () {
|
||||||
|
// selected with no arguments should give the current
|
||||||
|
// selection
|
||||||
|
var elements;
|
||||||
|
|
||||||
testModel.modified = 1;
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
|
||||||
var elements = controller.getElements();
|
elements = controller.getElements();
|
||||||
selectable[0].context.elementProxy = elements[1];
|
controller.select(elements[1]);
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
mockScope.selection.get.andReturn(elements[1]);
|
||||||
|
expect(controller.selected()).toEqual(elements[1]);
|
||||||
|
});
|
||||||
|
|
||||||
elements[1].remove();
|
it("allows selections to be cleared", function () {
|
||||||
testModel.modified = 2;
|
var elements;
|
||||||
|
|
||||||
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
|
||||||
expect($element[0].click).toHaveBeenCalled();
|
elements = controller.getElements();
|
||||||
|
controller.select(elements[1]);
|
||||||
|
controller.clearSelection();
|
||||||
|
expect(controller.selected(elements[1])).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("retains selections during refresh", function () {
|
it("retains selections during refresh", function () {
|
||||||
@ -374,21 +352,23 @@ define(
|
|||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
|
||||||
elements = controller.getElements();
|
elements = controller.getElements();
|
||||||
selectable[0].context.elementProxy = elements[1];
|
controller.select(elements[1]);
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
expect(controller.getSelectedElement()).toEqual(elements[1]);
|
// Verify precondition
|
||||||
|
expect(mockScope.selection.select.calls.length).toEqual(1);
|
||||||
|
|
||||||
|
// Mimic selection behavior
|
||||||
|
mockScope.selection.get.andReturn(elements[1]);
|
||||||
|
|
||||||
elements[2].remove();
|
elements[2].remove();
|
||||||
testModel.modified = 2;
|
testModel.modified = 2;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
|
||||||
elements = controller.getElements();
|
elements = controller.getElements();
|
||||||
|
|
||||||
// Verify removal, as test assumes this
|
// Verify removal, as test assumes this
|
||||||
expect(elements.length).toEqual(2);
|
expect(elements.length).toEqual(2);
|
||||||
|
|
||||||
expect(controller.shouldSelect(elements[1])).toBe(true);
|
expect(mockScope.selection.select.calls.length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Displays received values for telemetry elements", function () {
|
it("Displays received values for telemetry elements", function () {
|
||||||
@ -525,25 +505,21 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("exposes a view-level selection proxy", function () {
|
it("exposes a view-level selection proxy", function () {
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
expect(mockScope.selection.proxy).toHaveBeenCalledWith(
|
||||||
var selection = mockOpenMCT.selection.select.mostRecentCall.args[0];
|
jasmine.any(Object)
|
||||||
|
);
|
||||||
expect(mockOpenMCT.selection.select).toHaveBeenCalled();
|
|
||||||
expect(selection.context.viewProxy).toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exposes drag handles", function () {
|
it("exposes drag handles", function () {
|
||||||
var handles;
|
var handles;
|
||||||
|
|
||||||
|
// Select something so that drag handles are expected
|
||||||
testModel.modified = 1;
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
controller.select(controller.getElements()[1]);
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
// Should have a non-empty array of handles
|
// Should have a non-empty array of handles
|
||||||
handles = controller.handles();
|
handles = controller.handles();
|
||||||
|
|
||||||
expect(handles).toEqual(jasmine.any(Array));
|
expect(handles).toEqual(jasmine.any(Array));
|
||||||
expect(handles.length).not.toEqual(0);
|
expect(handles.length).not.toEqual(0);
|
||||||
|
|
||||||
@ -556,14 +532,15 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("exposes a move handle", function () {
|
it("exposes a move handle", function () {
|
||||||
|
var handle;
|
||||||
|
|
||||||
|
// Select something so that drag handles are expected
|
||||||
testModel.modified = 1;
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
controller.select(controller.getElements()[1]);
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
// Should have a move handle
|
// Should have a move handle
|
||||||
var handle = controller.moveHandle();
|
handle = controller.moveHandle();
|
||||||
|
|
||||||
// And it should have start/continue/end drag methods
|
// And it should have start/continue/end drag methods
|
||||||
expect(handle.startDrag).toEqual(jasmine.any(Function));
|
expect(handle.startDrag).toEqual(jasmine.any(Function));
|
||||||
@ -574,40 +551,26 @@ define(
|
|||||||
it("updates selection style during drag", function () {
|
it("updates selection style during drag", function () {
|
||||||
var oldStyle;
|
var oldStyle;
|
||||||
|
|
||||||
|
// Select something so that drag handles are expected
|
||||||
testModel.modified = 1;
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
controller.select(controller.getElements()[1]);
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
mockScope.selection.get.andReturn(controller.getElements()[1]);
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
// Get style
|
// Get style
|
||||||
oldStyle = controller.getSelectedElementStyle();
|
oldStyle = controller.selected().style;
|
||||||
|
|
||||||
// Start a drag gesture
|
// Start a drag gesture
|
||||||
controller.moveHandle().startDrag();
|
controller.moveHandle().startDrag();
|
||||||
|
|
||||||
// Haven't moved yet; style shouldn't have updated yet
|
// Haven't moved yet; style shouldn't have updated yet
|
||||||
expect(controller.getSelectedElementStyle()).toEqual(oldStyle);
|
expect(controller.selected().style).toEqual(oldStyle);
|
||||||
|
|
||||||
// Drag a little
|
// Drag a little
|
||||||
controller.moveHandle().continueDrag([1000, 100]);
|
controller.moveHandle().continueDrag([1000, 100]);
|
||||||
|
|
||||||
// Style should have been updated
|
// Style should have been updated
|
||||||
expect(controller.getSelectedElementStyle()).not.toEqual(oldStyle);
|
expect(controller.selected().style).not.toEqual(oldStyle);
|
||||||
});
|
|
||||||
|
|
||||||
it("cleans up slection on scope destroy", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
|
||||||
'$destroy',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
|
|
||||||
mockScope.$on.mostRecentCall.args[1]();
|
|
||||||
|
|
||||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("on display bounds changes", function () {
|
describe("on display bounds changes", function () {
|
||||||
@ -690,39 +653,6 @@ define(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("selects an range value to display, if available", function () {
|
|
||||||
mockMetadata.valuesForHints.andReturn([
|
|
||||||
{
|
|
||||||
key: 'range',
|
|
||||||
source: 'range'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
var key = controller.chooseTelemetryKeyToDisplay(mockMetadata);
|
|
||||||
expect(key).toEqual('range');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("selects the first non-domain value to display, if no range available", function () {
|
|
||||||
mockMetadata.valuesForHints.andReturn([]);
|
|
||||||
mockMetadata.values.andReturn([
|
|
||||||
{
|
|
||||||
key: 'domain',
|
|
||||||
source: 'domain',
|
|
||||||
hints: {
|
|
||||||
domain: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'image',
|
|
||||||
source: 'image',
|
|
||||||
hints: {
|
|
||||||
image: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
var key = controller.chooseTelemetryKeyToDisplay(mockMetadata);
|
|
||||||
expect(key).toEqual('image');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("reflects limit status", function () {
|
it("reflects limit status", function () {
|
||||||
mockLimitEvaluator.evaluate.andReturn({cssClass: "alarm-a"});
|
mockLimitEvaluator.evaluate.andReturn({cssClass: "alarm-a"});
|
||||||
controller.updateView(mockTelemetryObject, [{
|
controller.updateView(mockTelemetryObject, [{
|
||||||
@ -739,14 +669,6 @@ define(
|
|||||||
expect(controller.getElements()[0].cssClass).toEqual("alarm-a");
|
expect(controller.getElements()[0].cssClass).toEqual("alarm-a");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("listens for selection change events", function () {
|
|
||||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,8 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
["../src/LayoutController"],
|
||||||
"../src/LayoutController",
|
function (LayoutController) {
|
||||||
"zepto"
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
LayoutController,
|
|
||||||
$
|
|
||||||
) {
|
|
||||||
|
|
||||||
describe("The Layout controller", function () {
|
describe("The Layout controller", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
@ -38,12 +32,7 @@ define(
|
|||||||
controller,
|
controller,
|
||||||
mockCompositionCapability,
|
mockCompositionCapability,
|
||||||
mockComposition,
|
mockComposition,
|
||||||
mockCompositionObjects,
|
mockCompositionObjects;
|
||||||
mockOpenMCT,
|
|
||||||
mockSelection,
|
|
||||||
mockDomainObjectCapability,
|
|
||||||
$element = [],
|
|
||||||
selectable = [];
|
|
||||||
|
|
||||||
function mockPromise(value) {
|
function mockPromise(value) {
|
||||||
return {
|
return {
|
||||||
@ -69,18 +58,21 @@ define(
|
|||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
},
|
|
||||||
getCapability: function () {
|
|
||||||
return mockDomainObjectCapability;
|
|
||||||
},
|
|
||||||
hasCapability: function (param) {
|
|
||||||
if (param === 'composition') {
|
|
||||||
return id !== 'b';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility function to find a watch for a given expression
|
||||||
|
function findWatch(expr) {
|
||||||
|
var watch;
|
||||||
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === expr) {
|
||||||
|
watch = call.args[1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return watch;
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockScope = jasmine.createSpyObj(
|
mockScope = jasmine.createSpyObj(
|
||||||
"$scope",
|
"$scope",
|
||||||
@ -96,6 +88,7 @@ define(
|
|||||||
mockComposition = ["a", "b", "c"];
|
mockComposition = ["a", "b", "c"];
|
||||||
mockCompositionObjects = mockComposition.map(mockDomainObject);
|
mockCompositionObjects = mockComposition.map(mockDomainObject);
|
||||||
|
|
||||||
|
|
||||||
testConfiguration = {
|
testConfiguration = {
|
||||||
panels: {
|
panels: {
|
||||||
a: {
|
a: {
|
||||||
@ -104,70 +97,27 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
mockDomainObjectCapability = jasmine.createSpyObj('capability',
|
|
||||||
['inEditContext']
|
|
||||||
);
|
|
||||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
||||||
|
|
||||||
mockScope.domainObject = mockDomainObject("mockDomainObject");
|
mockScope.domainObject = mockDomainObject("mockDomainObject");
|
||||||
mockScope.model = testModel;
|
mockScope.model = testModel;
|
||||||
mockScope.configuration = testConfiguration;
|
mockScope.configuration = testConfiguration;
|
||||||
|
mockScope.selection = jasmine.createSpyObj(
|
||||||
selectable[0] = {
|
'selection',
|
||||||
context: {
|
['select', 'get', 'selected', 'deselect']
|
||||||
oldItem: mockScope.domainObject
|
);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
|
||||||
'select',
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.andReturn(selectable);
|
|
||||||
mockOpenMCT = {
|
|
||||||
selection: mockSelection
|
|
||||||
};
|
|
||||||
|
|
||||||
$element = $('<div></div>');
|
|
||||||
$(document).find('body').append($element);
|
|
||||||
spyOn($element[0], 'click');
|
|
||||||
|
|
||||||
spyOn(mockScope.domainObject, "useCapability").andCallThrough();
|
spyOn(mockScope.domainObject, "useCapability").andCallThrough();
|
||||||
|
|
||||||
controller = new LayoutController(mockScope, $element, mockOpenMCT);
|
controller = new LayoutController(mockScope);
|
||||||
spyOn(controller, "layoutPanels").andCallThrough();
|
spyOn(controller, "layoutPanels").andCallThrough();
|
||||||
|
|
||||||
|
findWatch("selection")(mockScope.selection);
|
||||||
|
|
||||||
jasmine.Clock.useMock();
|
jasmine.Clock.useMock();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
$element.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("listens for selection change events", function () {
|
|
||||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Model changes will indicate that panel positions
|
// Model changes will indicate that panel positions
|
||||||
// may have changed, for instance.
|
// may have changed, for instance.
|
||||||
it("watches for changes to composition", function () {
|
it("watches for changes to composition", function () {
|
||||||
@ -370,35 +320,67 @@ define(
|
|||||||
.not.toEqual(oldStyle);
|
.not.toEqual(oldStyle);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows objects to be selected", function () {
|
it("allows panels to be selected", function () {
|
||||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||||
var childObj = mockCompositionObjects[0];
|
var childObj = mockCompositionObjects[0];
|
||||||
selectable[0].context.oldItem = childObj;
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
controller.select(mockEvent, childObj.getId());
|
||||||
|
|
||||||
|
expect(mockEvent.stopPropagation).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(controller.selected(childObj)).toBe(true);
|
expect(controller.selected(childObj)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prevents event bubbling while drag is in progress", function () {
|
it("allows selection to be cleared", function () {
|
||||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||||
var childObj = mockCompositionObjects[0];
|
var childObj = mockCompositionObjects[0];
|
||||||
|
|
||||||
|
controller.select(null, childObj.getId());
|
||||||
|
controller.clearSelection();
|
||||||
|
|
||||||
|
expect(controller.selected(childObj)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prevents clearing selection while drag is in progress", function () {
|
||||||
|
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||||
|
var childObj = mockCompositionObjects[0];
|
||||||
|
var id = childObj.getId();
|
||||||
|
|
||||||
|
controller.select(mockEvent, id);
|
||||||
|
|
||||||
// Do a drag
|
// Do a drag
|
||||||
controller.startDrag(childObj.getId(), [1, 1], [0, 0]);
|
controller.startDrag(id, [1, 1], [0, 0]);
|
||||||
controller.continueDrag([100, 100]);
|
controller.continueDrag([100, 100]);
|
||||||
controller.endDrag();
|
controller.endDrag();
|
||||||
|
|
||||||
// Because mouse position could cause the parent object to be selected, this should be ignored.
|
// Because mouse position could cause clearSelection to be called, this should be ignored.
|
||||||
controller.bypassSelection(mockEvent);
|
controller.clearSelection();
|
||||||
|
|
||||||
expect(mockEvent.stopPropagation).toHaveBeenCalled();
|
expect(controller.selected(childObj)).toBe(true);
|
||||||
|
|
||||||
// Shoud be able to select another object when dragging is done.
|
// Shoud be able to clear the selection after dragging is done.
|
||||||
jasmine.Clock.tick(0);
|
jasmine.Clock.tick(0);
|
||||||
mockEvent.stopPropagation.reset();
|
controller.clearSelection();
|
||||||
controller.bypassSelection(mockEvent);
|
|
||||||
|
|
||||||
expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
|
expect(controller.selected(childObj)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears selection after moving/resizing", function () {
|
||||||
|
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||||
|
var childObj = mockCompositionObjects[0];
|
||||||
|
var id = childObj.getId();
|
||||||
|
|
||||||
|
controller.select(mockEvent, id);
|
||||||
|
|
||||||
|
// Do a drag
|
||||||
|
controller.startDrag(id, [1, 1], [0, 0]);
|
||||||
|
controller.continueDrag([100, 100]);
|
||||||
|
controller.endDrag();
|
||||||
|
|
||||||
|
jasmine.Clock.tick(0);
|
||||||
|
controller.clearSelection();
|
||||||
|
|
||||||
|
expect(controller.selected(childObj)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows frames by default", function () {
|
it("shows frames by default", function () {
|
||||||
@ -416,74 +398,43 @@ define(
|
|||||||
it("hides frame when selected object has frame ", function () {
|
it("hides frame when selected object has frame ", function () {
|
||||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||||
var childObj = mockCompositionObjects[0];
|
var childObj = mockCompositionObjects[0];
|
||||||
selectable[0].context.oldItem = childObj;
|
controller.select(mockEvent, childObj.getId());
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
|
expect(mockScope.selection.select).toHaveBeenCalled();
|
||||||
|
|
||||||
|
var selectedObj = mockScope.selection.select.mostRecentCall.args[0];
|
||||||
|
|
||||||
expect(controller.hasFrame(childObj)).toBe(true);
|
expect(controller.hasFrame(childObj)).toBe(true);
|
||||||
expect(toolbarObj.hideFrame).toBeDefined();
|
expect(selectedObj.hideFrame).toBeDefined();
|
||||||
expect(toolbarObj.hideFrame).toEqual(jasmine.any(Function));
|
expect(selectedObj.hideFrame).toEqual(jasmine.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows frame when selected object has no frame", function () {
|
it("shows frame when selected object has no frame", function () {
|
||||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||||
|
|
||||||
var childObj = mockCompositionObjects[1];
|
var childObj = mockCompositionObjects[1];
|
||||||
selectable[0].context.oldItem = childObj;
|
controller.select(mockEvent, childObj.getId());
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
|
expect(mockScope.selection.select).toHaveBeenCalled();
|
||||||
|
|
||||||
|
var selectedObj = mockScope.selection.select.mostRecentCall.args[0];
|
||||||
|
|
||||||
expect(controller.hasFrame(childObj)).toBe(false);
|
expect(controller.hasFrame(childObj)).toBe(false);
|
||||||
expect(toolbarObj.showFrame).toBeDefined();
|
expect(selectedObj.showFrame).toBeDefined();
|
||||||
expect(toolbarObj.showFrame).toEqual(jasmine.any(Function));
|
expect(selectedObj.showFrame).toEqual(jasmine.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("selects the parent object when selected object is removed", function () {
|
it("deselects the object that is no longer in the composition", function () {
|
||||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||||
var childObj = mockCompositionObjects[0];
|
var childObj = mockCompositionObjects[0];
|
||||||
selectable[0].context.oldItem = childObj;
|
controller.select(mockEvent, childObj.getId());
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
var composition = ["b", "c"];
|
var composition = ["b", "c"];
|
||||||
mockScope.$watchCollection.mostRecentCall.args[1](composition);
|
mockScope.$watchCollection.mostRecentCall.args[1](composition);
|
||||||
|
|
||||||
expect($element[0].click).toHaveBeenCalled();
|
expect(controller.selected(childObj)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows objects to be drilled-in only when editing", function () {
|
|
||||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
|
||||||
var childObj = mockCompositionObjects[0];
|
|
||||||
childObj.getCapability().inEditContext.andReturn(false);
|
|
||||||
controller.drill(mockEvent, childObj);
|
|
||||||
|
|
||||||
expect(controller.isDrilledIn(childObj)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows objects to be drilled-in only if it has sub objects", function () {
|
|
||||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
|
||||||
var childObj = mockCompositionObjects[1];
|
|
||||||
childObj.getCapability().inEditContext.andReturn(true);
|
|
||||||
controller.drill(mockEvent, childObj);
|
|
||||||
|
|
||||||
expect(controller.isDrilledIn(childObj)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("selects a newly-dropped object", function () {
|
|
||||||
mockScope.$on.mostRecentCall.args[1](
|
|
||||||
mockEvent,
|
|
||||||
'd',
|
|
||||||
{ x: 300, y: 100 }
|
|
||||||
);
|
|
||||||
|
|
||||||
var childObj = mockDomainObject("d");
|
|
||||||
var testElement = $("<div data-layout-id='some-id'></div>");
|
|
||||||
$element.append(testElement);
|
|
||||||
spyOn(testElement[0], 'click');
|
|
||||||
|
|
||||||
controller.selectIfNew('some-id', childObj);
|
|
||||||
jasmine.Clock.tick(0);
|
|
||||||
|
|
||||||
expect(testElement[0].click).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -69,7 +69,7 @@ define([
|
|||||||
"delegates": [
|
"delegates": [
|
||||||
"telemetry"
|
"telemetry"
|
||||||
],
|
],
|
||||||
"inspector": "table-options-edit",
|
"inspector": tableInspector,
|
||||||
"contains": [
|
"contains": [
|
||||||
{
|
{
|
||||||
"has": "telemetry"
|
"has": "telemetry"
|
||||||
|
@ -19,10 +19,7 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
|
<div ng-controller="TableOptionsController" class="l-controls-first flex-elem grows l-inspector-part">
|
||||||
<div ng-if="domainObject.getCapability('editor').inEditContext()"
|
|
||||||
ng-controller="TableOptionsController"
|
|
||||||
class="l-controls-first flex-elem grows l-inspector-part">
|
|
||||||
<em class="t-inspector-part-header" title="Display properties for this object">Table Options</em>
|
<em class="t-inspector-part-header" title="Display properties for this object">Table Options</em>
|
||||||
<mct-form
|
<mct-form
|
||||||
ng-model="configuration.table.columns"
|
ng-model="configuration.table.columns"
|
||||||
|
@ -32,7 +32,6 @@ define(
|
|||||||
*/
|
*/
|
||||||
function TelemetryCollection() {
|
function TelemetryCollection() {
|
||||||
EventEmitter.call(this, arguments);
|
EventEmitter.call(this, arguments);
|
||||||
this.dupeCheck = false;
|
|
||||||
this.telemetry = [];
|
this.telemetry = [];
|
||||||
this.highBuffer = [];
|
this.highBuffer = [];
|
||||||
this.sortField = undefined;
|
this.sortField = undefined;
|
||||||
@ -162,7 +161,7 @@ define(
|
|||||||
var startIx = _.sortedIndex(array, item, this.sortField);
|
var startIx = _.sortedIndex(array, item, this.sortField);
|
||||||
var endIx;
|
var endIx;
|
||||||
|
|
||||||
if (this.dupeCheck && startIx !== array.length) {
|
if (startIx !== array.length) {
|
||||||
endIx = _.sortedLastIndex(array, item, this.sortField);
|
endIx = _.sortedLastIndex(array, item, this.sortField);
|
||||||
|
|
||||||
// Create an array of potential dupes, based on having the
|
// Create an array of potential dupes, based on having the
|
||||||
@ -190,7 +189,6 @@ define(
|
|||||||
TelemetryCollection.prototype.add = function (items) {
|
TelemetryCollection.prototype.add = function (items) {
|
||||||
var added = items.filter(this.addOne);
|
var added = items.filter(this.addOne);
|
||||||
this.emit('added', added);
|
this.emit('added', added);
|
||||||
this.dupeCheck = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -436,31 +436,9 @@ define(
|
|||||||
* @param {Object} searchElement Object to find the insertion point for
|
* @param {Object} searchElement Object to find the insertion point for
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.findInsertionPoint = function (searchArray, searchElement) {
|
MCTTableController.prototype.findInsertionPoint = function (searchArray, searchElement) {
|
||||||
var index;
|
//First, use a binary search to find the correct insertion point
|
||||||
var testIndex;
|
var index = this.binarySearch(searchArray, searchElement, 0, searchArray.length - 1);
|
||||||
var first = searchArray[0];
|
var testIndex = index;
|
||||||
var last = searchArray[searchArray.length - 1];
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
first = first[this.$scope.sortColumn].text;
|
|
||||||
}
|
|
||||||
if (last) {
|
|
||||||
last = last[this.$scope.sortColumn].text;
|
|
||||||
}
|
|
||||||
// Shortcut check for append/prepend
|
|
||||||
if (first && this.sortComparator(first, searchElement) >= 0) {
|
|
||||||
index = testIndex = 0;
|
|
||||||
} else if (last && this.sortComparator(last, searchElement) <= 0) {
|
|
||||||
index = testIndex = searchArray.length;
|
|
||||||
} else {
|
|
||||||
// use a binary search to find the correct insertion point
|
|
||||||
index = testIndex = this.binarySearch(
|
|
||||||
searchArray,
|
|
||||||
searchElement,
|
|
||||||
0,
|
|
||||||
searchArray.length - 1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//It's possible that the insertion point is a duplicate of the element to be inserted
|
//It's possible that the insertion point is a duplicate of the element to be inserted
|
||||||
var isDupe = function () {
|
var isDupe = function () {
|
||||||
|
@ -170,9 +170,6 @@ define(
|
|||||||
* @param rows
|
* @param rows
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.addRowsToTable = function (rows) {
|
TelemetryTableController.prototype.addRowsToTable = function (rows) {
|
||||||
rows.forEach(function (row) {
|
|
||||||
this.$scope.rows.push(row);
|
|
||||||
}, this);
|
|
||||||
this.$scope.$broadcast('add:rows', rows);
|
this.$scope.$broadcast('add:rows', rows);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -436,28 +436,5 @@ define(
|
|||||||
expect(mockScope.$broadcast).toHaveBeenCalledWith("remove:rows", discardedRows);
|
expect(mockScope.$broadcast).toHaveBeenCalledWith("remove:rows", discardedRows);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when telemetry is added', function () {
|
|
||||||
var testRows;
|
|
||||||
var expectedRows;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
testRows = [{ a: 0 }, { a: 1 }, { a: 2 }];
|
|
||||||
mockScope.rows = [{ a: -1 }];
|
|
||||||
expectedRows = mockScope.rows.concat(testRows);
|
|
||||||
|
|
||||||
spyOn(controller.telemetry, "on").andCallThrough();
|
|
||||||
controller.registerChangeListeners();
|
|
||||||
|
|
||||||
controller.telemetry.on.calls.forEach(function (call) {
|
|
||||||
if (call.args[0] === 'added') {
|
|
||||||
call.args[1](testRows);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds it to rows in scope", function () {
|
|
||||||
expect(mockScope.rows).toEqual(expectedRows);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -38,7 +38,6 @@ define([
|
|||||||
"./src/capabilities/CostCapability",
|
"./src/capabilities/CostCapability",
|
||||||
"./src/directives/MCTSwimlaneDrop",
|
"./src/directives/MCTSwimlaneDrop",
|
||||||
"./src/directives/MCTSwimlaneDrag",
|
"./src/directives/MCTSwimlaneDrag",
|
||||||
"./src/directives/MCTResourceGraphDrop",
|
|
||||||
"./src/services/ObjectLoader",
|
"./src/services/ObjectLoader",
|
||||||
"./src/chart/MCTTimelineChart",
|
"./src/chart/MCTTimelineChart",
|
||||||
"text!./res/templates/values.html",
|
"text!./res/templates/values.html",
|
||||||
@ -70,7 +69,6 @@ define([
|
|||||||
CostCapability,
|
CostCapability,
|
||||||
MCTSwimlaneDrop,
|
MCTSwimlaneDrop,
|
||||||
MCTSwimlaneDrag,
|
MCTSwimlaneDrag,
|
||||||
MCTResourceGraphDrop,
|
|
||||||
ObjectLoader,
|
ObjectLoader,
|
||||||
MCTTimelineChart,
|
MCTTimelineChart,
|
||||||
valuesTemplate,
|
valuesTemplate,
|
||||||
@ -579,13 +577,6 @@ define([
|
|||||||
"$interval",
|
"$interval",
|
||||||
"$log"
|
"$log"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mctResourceGraphDrop",
|
|
||||||
"implementation": MCTResourceGraphDrop,
|
|
||||||
"depends": [
|
|
||||||
"dndService"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"services": [
|
"services": [
|
||||||
|
@ -38,10 +38,6 @@
|
|||||||
.l-timeline-pane {
|
.l-timeline-pane {
|
||||||
@include absPosDefault();
|
@include absPosDefault();
|
||||||
|
|
||||||
&.drop-over {
|
|
||||||
background-color: lighten($colorEditAreaBg, 5%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-width-control {
|
.l-width-control {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class="s-timeline l-timeline-holder split-layout vertical splitter-sm"
|
<div class="s-timeline l-timeline-holder split-layout vertical splitter-sm"
|
||||||
ng-click="$event.stopPropagation()"
|
|
||||||
ng-controller="TimelineController as timelineController">
|
ng-controller="TimelineController as timelineController">
|
||||||
|
|
||||||
<mct-split-pane anchor="left" class="abs" position="pane.x">
|
<mct-split-pane anchor="left" class="abs" position="pane.x">
|
||||||
@ -78,8 +77,7 @@
|
|||||||
<mct-splitter></mct-splitter>
|
<mct-splitter></mct-splitter>
|
||||||
|
|
||||||
<!-- BOTTOM PANE RESOURCE LEGEND -->
|
<!-- BOTTOM PANE RESOURCE LEGEND -->
|
||||||
<div mct-resource-graph-drop
|
<div class="split-pane-component abs l-timeline-pane t-pane-h l-pane-btm s-timeline-resource-legend l-timeline-resource-legend">
|
||||||
class="split-pane-component abs l-timeline-pane t-pane-h l-pane-btm s-timeline-resource-legend l-timeline-resource-legend">
|
|
||||||
<div class="l-title s-title">{{ngModel.title}}Resource Graph Legend</div>
|
<div class="l-title s-title">{{ngModel.title}}Resource Graph Legend</div>
|
||||||
<div class="l-legend-items legend">
|
<div class="l-legend-items legend">
|
||||||
<mct-include key="'timeline-legend-item'"
|
<mct-include key="'timeline-legend-item'"
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
['./SwimlaneDragConstants'],
|
|
||||||
function (SwimlaneDragConstants) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the `mct-resource-graph-drop` directive. When a drop occurs
|
|
||||||
* on an element with this attribute, the swimlane targeted by the drop
|
|
||||||
* will receive the dropped domain object (at which point it can handle
|
|
||||||
* the drop, typically by toggling the swimlane graph.)
|
|
||||||
* @param {DndService} dndService drag-and-drop service
|
|
||||||
*/
|
|
||||||
function MCTResourceGraphDrop(dndService) {
|
|
||||||
|
|
||||||
function link(scope, element, attrs) {
|
|
||||||
// Handle dragover
|
|
||||||
element.on('dragover', function (e) {
|
|
||||||
var swimlane = dndService.getData(
|
|
||||||
SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE
|
|
||||||
);
|
|
||||||
|
|
||||||
if (typeof swimlane !== "undefined" && !swimlane.graph()) {
|
|
||||||
element.addClass('drop-over');
|
|
||||||
scope.$apply();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Handle drops
|
|
||||||
element.on('drop', function (e) {
|
|
||||||
var swimlane = dndService.getData(
|
|
||||||
SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE
|
|
||||||
);
|
|
||||||
|
|
||||||
element.removeClass('drop-over');
|
|
||||||
|
|
||||||
// Only toggle if the graph isn't already set
|
|
||||||
if (typeof swimlane !== "undefined" && !swimlane.graph()) {
|
|
||||||
swimlane.toggleGraph();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Clear highlights when drag leaves this swimlane
|
|
||||||
element.on('dragleave', function (e) {
|
|
||||||
element.removeClass('drop-over');
|
|
||||||
scope.$apply();
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Applies to attributes
|
|
||||||
restrict: "A",
|
|
||||||
// Link using above function
|
|
||||||
link: link
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return MCTResourceGraphDrop;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,159 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
['../../src/directives/MCTResourceGraphDrop', '../../src/directives/SwimlaneDragConstants'],
|
|
||||||
function (MCTResourceGraphDrop, SwimlaneDragConstants) {
|
|
||||||
|
|
||||||
describe("The mct-resource-graph-drop directive", function () {
|
|
||||||
var mockDndService,
|
|
||||||
mockScope,
|
|
||||||
mockElement,
|
|
||||||
testAttrs,
|
|
||||||
mockSwimlane,
|
|
||||||
testEvent,
|
|
||||||
handlers,
|
|
||||||
directive;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
handlers = {};
|
|
||||||
|
|
||||||
mockDndService = jasmine.createSpyObj(
|
|
||||||
'dndService',
|
|
||||||
['setData', 'getData', 'removeData']
|
|
||||||
);
|
|
||||||
mockScope = jasmine.createSpyObj('$scope', ['$eval', '$apply']);
|
|
||||||
mockElement = jasmine.createSpyObj('element', ['on', 'addClass', 'removeClass']);
|
|
||||||
testAttrs = { mctSwimlaneDrop: "mockSwimlane" };
|
|
||||||
mockSwimlane = jasmine.createSpyObj(
|
|
||||||
"swimlane",
|
|
||||||
['graph', 'toggleGraph']
|
|
||||||
);
|
|
||||||
|
|
||||||
testEvent = {
|
|
||||||
dataTransfer: { getData: jasmine.createSpy() },
|
|
||||||
preventDefault: jasmine.createSpy(),
|
|
||||||
stopPropagation: jasmine.createSpy()
|
|
||||||
};
|
|
||||||
|
|
||||||
testEvent.dataTransfer.getData.andReturn('abc');
|
|
||||||
mockDndService.getData.andCallFake(function (key) {
|
|
||||||
return key === SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE ?
|
|
||||||
mockSwimlane : undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
mockSwimlane.graph.andReturn(false);
|
|
||||||
|
|
||||||
directive = new MCTResourceGraphDrop(mockDndService);
|
|
||||||
directive.link(mockScope, mockElement, testAttrs);
|
|
||||||
|
|
||||||
mockElement.on.calls.forEach(function (call) {
|
|
||||||
handlers[call.args[0]] = call.args[1];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("is available as an attribute", function () {
|
|
||||||
expect(directive.restrict).toEqual("A");
|
|
||||||
});
|
|
||||||
|
|
||||||
[false, true].forEach(function (graphing) {
|
|
||||||
describe("when swimlane graph is " + (graphing ? "" : "not ") + "enabled", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
mockSwimlane.graph.andReturn(graphing);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe("on dragover", function () {
|
|
||||||
var prefix = !graphing ? "does" : "does not";
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
handlers.dragover(testEvent);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(prefix + " add a drop-over class", function () {
|
|
||||||
var expectAddClass = expect(mockElement.addClass);
|
|
||||||
(!graphing ? expectAddClass : expectAddClass.not)
|
|
||||||
.toHaveBeenCalledWith('drop-over');
|
|
||||||
});
|
|
||||||
|
|
||||||
it(prefix + " call $apply on scope", function () {
|
|
||||||
var expectApply = expect(mockScope.$apply);
|
|
||||||
(!graphing ? expectApply : expectApply.not)
|
|
||||||
.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(prefix + " prevent default", function () {
|
|
||||||
var expectPreventDefault = expect(testEvent.preventDefault);
|
|
||||||
(!graphing ? expectPreventDefault : expectPreventDefault.not)
|
|
||||||
.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("on drop", function () {
|
|
||||||
var prefix = !graphing ? "does" : "does not";
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
handlers.drop(testEvent);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("removes any drop-over class", function () {
|
|
||||||
expect(mockElement.removeClass)
|
|
||||||
.toHaveBeenCalledWith('drop-over');
|
|
||||||
});
|
|
||||||
|
|
||||||
it(prefix + " toggle the swimlane's resource graph", function () {
|
|
||||||
var expectToggle = expect(mockSwimlane.toggleGraph);
|
|
||||||
(!graphing ? expectToggle : expectToggle.not)
|
|
||||||
.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(prefix + " prevent default", function () {
|
|
||||||
var expectPreventDefault = expect(testEvent.preventDefault);
|
|
||||||
(!graphing ? expectPreventDefault : expectPreventDefault.not)
|
|
||||||
.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("on dragleave", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
handlers.dragleave(testEvent);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("removes any drop-over class", function () {
|
|
||||||
expect(mockElement.removeClass)
|
|
||||||
.toHaveBeenCalledWith('drop-over');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calls $apply on scope", function () {
|
|
||||||
expect(mockScope.$apply).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calls preventDefault on events", function () {
|
|
||||||
expect(testEvent.preventDefault).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user