Compare commits
105 Commits
enumerated
...
open769_re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f34e8ba61b | ||
|
|
2fb9b65652 | ||
|
|
0c6b4a5a23 | ||
|
|
20672ad028 | ||
|
|
99ba9edb95 | ||
|
|
23a8c305c1 | ||
|
|
c591ade479 | ||
|
|
8f4f0cb78e | ||
|
|
e87280aa15 | ||
|
|
e0a2d02d23 | ||
|
|
73012233b8 | ||
|
|
339916ccd4 | ||
|
|
70acef6905 | ||
|
|
da09ffd3fa | ||
|
|
5b98da6681 | ||
|
|
2040abb768 | ||
|
|
d35fccbbe8 | ||
|
|
06c6832676 | ||
|
|
f49552779a | ||
|
|
200a426f17 | ||
|
|
e068173f3e | ||
|
|
4441e88769 | ||
|
|
254a944d7a | ||
|
|
0c00061cbc | ||
|
|
dee0613b81 | ||
|
|
6b6bada700 | ||
|
|
530b940a64 | ||
|
|
e23bf5ed39 | ||
|
|
6ecea9950d | ||
|
|
4816dddf41 | ||
|
|
cae775f9bc | ||
|
|
a4b79cdb5b | ||
|
|
012a38cccd | ||
|
|
a01f7ddd2d | ||
|
|
e7e91e21fc | ||
|
|
cbea842c8b | ||
|
|
cc5d14deec | ||
|
|
4c5217d646 | ||
|
|
5e54b193ca | ||
|
|
cd7ff8ad85 | ||
|
|
fa5d59bff8 | ||
|
|
ddbb72b88a | ||
|
|
88784d37fd | ||
|
|
700e605bbd | ||
|
|
594f3b8ec2 | ||
|
|
b08d00ef3e | ||
|
|
0643fb1f3f | ||
|
|
289debf19d | ||
|
|
7da1a4b2a3 | ||
|
|
528169de2c | ||
|
|
c13231b8e8 | ||
|
|
3e4a3aeb9b | ||
|
|
650ef9bfa4 | ||
|
|
dd053f7e6e | ||
|
|
cb655d486b | ||
|
|
3c52ceb71a | ||
|
|
b2337dea97 | ||
|
|
dea6554e04 | ||
|
|
31d8c9a48f | ||
|
|
f8682a7a29 | ||
|
|
e5544615cc | ||
|
|
ec0cc572f6 | ||
|
|
6c2a28aba2 | ||
|
|
baccd005dc | ||
|
|
1e4ff5a73f | ||
|
|
9f29382e18 | ||
|
|
5f6b4adcda | ||
|
|
d6ab70447e | ||
|
|
a411bac331 | ||
|
|
5624c7d545 | ||
|
|
007741b4e7 | ||
|
|
e0a69744e5 | ||
|
|
e7a6c34bcc | ||
|
|
7f3ac4077c | ||
|
|
7eaffdc34a | ||
|
|
5034e88656 | ||
|
|
aa48044345 | ||
|
|
d12111d9b8 | ||
|
|
9c9db3c24f | ||
|
|
3fe41575bd | ||
|
|
8fa030437e | ||
|
|
17faf000b0 | ||
|
|
90c82f6ef2 | ||
|
|
5e6fe16b93 | ||
|
|
f0ca6fdfdb | ||
|
|
ad4c456ca2 | ||
|
|
b0476edb8c | ||
|
|
8f6a287fb8 | ||
|
|
07d554d114 | ||
|
|
24d2cecdcd | ||
|
|
8efa9c6aac | ||
|
|
22e98274ca | ||
|
|
471b57f47c | ||
|
|
9c775c6715 | ||
|
|
1f09b7b0ac | ||
|
|
b0cf9bbd29 | ||
|
|
4a1ca25e17 | ||
|
|
cc97e567b6 | ||
|
|
53f03cddb7 | ||
|
|
da1e6750a0 | ||
|
|
3258342783 | ||
|
|
73b7365ae2 | ||
|
|
32a42bd679 | ||
|
|
324c2cac03 | ||
|
|
42ac657105 |
24
LICENSES.md
24
LICENSES.md
@@ -309,30 +309,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
|
||||
---
|
||||
|
||||
### Modernizr
|
||||
|
||||
#### Info
|
||||
|
||||
* Link: http://modernizr.com
|
||||
|
||||
* Version: 2.6.2
|
||||
|
||||
* Author: Faruk Ateş
|
||||
|
||||
* Description: Browser/device capability finding
|
||||
|
||||
#### License
|
||||
|
||||
Copyright (c) 2009–2015
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
### Normalize.css
|
||||
|
||||
#### Info
|
||||
|
||||
@@ -6,12 +6,13 @@ Victor Woeltjen
|
||||
September 23, 2015
|
||||
Document Version 1.1
|
||||
|
||||
Date | Version | Summary of Changes | Author
|
||||
------------------- | --------- | ----------------------- | ---------------
|
||||
April 29, 2015 | 0 | Initial Draft | Victor Woeltjen
|
||||
May 12, 2015 | 0.1 | | Victor Woeltjen
|
||||
June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen
|
||||
October 4, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry
|
||||
Date | Version | Summary of Changes | Author
|
||||
------------------- | --------- | ------------------------- | ---------------
|
||||
April 29, 2015 | 0 | Initial Draft | Victor Woeltjen
|
||||
May 12, 2015 | 0.1 | | Victor Woeltjen
|
||||
June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen
|
||||
October 4, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry
|
||||
April 5, 2016 | 1.2 | Added Mct-table directive | Andrew Henry
|
||||
|
||||
# Introduction
|
||||
The purpose of this guide is to familiarize software developers with the Open
|
||||
@@ -1600,6 +1601,61 @@ there are items .
|
||||
]
|
||||
}
|
||||
|
||||
## Table
|
||||
|
||||
The `mct-table` directive provides a generic table component, with optional
|
||||
sorting and filtering capabilities. The table can be pre-populated with data
|
||||
by setting the `rows` parameter, and it can be updated in real-time using the
|
||||
`add:row` and `remove:row` broadcast events. The table will expand to occupy
|
||||
100% of the size of its containing element. The table is highly optimized for
|
||||
very large data sets.
|
||||
|
||||
### Events
|
||||
|
||||
The table supports two events for notifying that the rows have changed. For
|
||||
performance reasons, the table does not monitor the content of `rows`
|
||||
constantly.
|
||||
|
||||
* `add:row`: A `$broadcast` event that will notify the table that a new row
|
||||
has been added to the table.
|
||||
|
||||
eg. The code below adds a new row, and alerts the table using the `add:row`
|
||||
event. Sorting and filtering will be applied automatically by the table component.
|
||||
|
||||
```
|
||||
$scope.rows.push(newRow);
|
||||
$scope.$broadcast('add:row', $scope.rows.length-1);
|
||||
```
|
||||
|
||||
* `remove:row`: A `$broadcast` event that will notify the table that a row
|
||||
should be removed from the table.
|
||||
|
||||
eg. The code below removes a row from the rows array, and then alerts the table
|
||||
to its removal.
|
||||
|
||||
```
|
||||
$scope.rows.slice(5, 1);
|
||||
$scope.$broadcast('remove:row', 5);
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
* `headers`: An array of string values which will constitute the column titles
|
||||
that appear at the top of the table. Corresponding values are specified in
|
||||
the rows using the header title provided here.
|
||||
* `rows`: An array of objects containing row values. Each element in the
|
||||
array must be an associative array, where the key corresponds to a column header.
|
||||
* `enableFilter`: A boolean that if true, will enable searching and result
|
||||
filtering. When enabled, each column will have a text input field that can be
|
||||
used to filter the table rows in real time.
|
||||
* `enableSort`: A boolean determining whether rows can be sorted. If true,
|
||||
sorting will be enabled allowing sorting by clicking on column headers. Only
|
||||
one column may be sorted at a time.
|
||||
* `autoScroll`: A boolean value that if true, will cause the table to automatically
|
||||
scroll to the bottom as new data arrives. Auto-scroll can be disengaged manually
|
||||
by scrolling away from the bottom of the table, and can also be enabled manually
|
||||
by scrolling to the bottom of the table rows.
|
||||
|
||||
# Services
|
||||
|
||||
The Open MCT Web platform provides a variety of services which can be retrieved
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
"./src/EnumeratedTelemetryProvider",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
EnumeratedTelemetryProvider,
|
||||
legacyRegistry
|
||||
) {
|
||||
"use strict";
|
||||
|
||||
legacyRegistry.register("example/enumeratedTelemetry", {
|
||||
"name": "Enumerated telemetry generator",
|
||||
"description": "Example telemetry source that provides enumerated telemetry.",
|
||||
"extensions": {
|
||||
"components": [
|
||||
{
|
||||
"implementation": EnumeratedTelemetryProvider,
|
||||
"type": "provider",
|
||||
"provides": "telemetryService",
|
||||
"depends": [
|
||||
"$q",
|
||||
"$interval"
|
||||
]
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"key": "example.telemetry.enumerated",
|
||||
"name": "Enumerated Telemetry Generator",
|
||||
"glyph": "T",
|
||||
"description": "Generated enumerated telemetry data",
|
||||
"features": "creation",
|
||||
"model": {
|
||||
"telemetry": {
|
||||
|
||||
}
|
||||
},
|
||||
"telemetry": {
|
||||
"source": "example.telemetry.enumerated",
|
||||
"domains": [
|
||||
{
|
||||
"key": "time",
|
||||
"name": "Time"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
{
|
||||
"key": "eu",
|
||||
"name": "Engineering Unit"
|
||||
},
|
||||
{
|
||||
"key": "eu",
|
||||
"name": "Enum",
|
||||
"type": "enum",
|
||||
"format": "enum",
|
||||
"enumerations": [
|
||||
{
|
||||
"key": "eu",
|
||||
"format": "enum",
|
||||
"value": 0,
|
||||
"string": "OFF"
|
||||
},
|
||||
{
|
||||
"key": "eu",
|
||||
"format": "enum",
|
||||
"value": 1,
|
||||
"string": "IDLE"
|
||||
},
|
||||
{
|
||||
"key": "eu",
|
||||
"format": "enum",
|
||||
"value": 2,
|
||||
"string": "RECEIVE"
|
||||
},
|
||||
{
|
||||
"key": "eu",
|
||||
"format": "enum",
|
||||
"value": 3,
|
||||
"string": "TRANSMIT"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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(
|
||||
["./EnumeratedTelemetrySeries"],
|
||||
function (EnumeratedTelemetrySeries) {
|
||||
"use strict";
|
||||
|
||||
function EnumeratedTelemetryProvider($q, $interval) {
|
||||
this.$q = $q;
|
||||
this.$interval = $interval;
|
||||
}
|
||||
|
||||
EnumeratedTelemetryProvider.prototype.matchRequest = function (
|
||||
request
|
||||
) {
|
||||
return request.source === 'example.telemetry.enumerated';
|
||||
};
|
||||
|
||||
// Return a telemetry series between start and end time with
|
||||
// 1000 telemetry points.
|
||||
EnumeratedTelemetryProvider.prototype.getSeries = function (
|
||||
start,
|
||||
end,
|
||||
request
|
||||
) {
|
||||
var elapsedSeconds = (end - start) / 1000,
|
||||
valueEvery = elapsedSeconds / 1000,
|
||||
currentSecond = 0,
|
||||
values = [],
|
||||
currentValue = 0;
|
||||
|
||||
while (currentSecond < elapsedSeconds) {
|
||||
values.push({
|
||||
time: +new Date(+start + (currentSecond * 1000)),
|
||||
eu: currentValue
|
||||
});
|
||||
if ((values.length % Math.pow(3, currentValue + 1)) === 0) {
|
||||
if (currentValue === 3) {
|
||||
currentValue = 0;
|
||||
} else {
|
||||
currentValue += 1;
|
||||
}
|
||||
}
|
||||
currentSecond += valueEvery;
|
||||
}
|
||||
|
||||
return new EnumeratedTelemetrySeries(values);
|
||||
};
|
||||
|
||||
EnumeratedTelemetryProvider.prototype.requestTelemetry = function (
|
||||
requests
|
||||
) {
|
||||
var validRequests = requests.filter(this.matchRequest),
|
||||
generatedTelemetry = {},
|
||||
response = {
|
||||
'example.telemetry.enumerated': generatedTelemetry
|
||||
};
|
||||
|
||||
validRequests.forEach(function (validRequest) {
|
||||
var start = validRequest.start,
|
||||
end = validRequest.end;
|
||||
|
||||
if (!end) {
|
||||
end = new Date(Date.now());
|
||||
}
|
||||
if (!start) {
|
||||
start = new Date(Date.UTC(
|
||||
end.getUTCFullYear(),
|
||||
end.getUTCMonth(),
|
||||
end.getUTCDate() - 1,
|
||||
end.getUTCHours(),
|
||||
end.getUTCMinutes(),
|
||||
end.getUTCSeconds(),
|
||||
end.getUTCMilliseconds()
|
||||
));
|
||||
}
|
||||
generatedTelemetry[validRequest.key] =
|
||||
this.getSeries(start, end);
|
||||
}, this);
|
||||
|
||||
return this.$q.resolve(response);
|
||||
};
|
||||
|
||||
EnumeratedTelemetryProvider.prototype.makeTelemetryEmitter = function (
|
||||
request,
|
||||
callback
|
||||
) {
|
||||
var valueEvery = 1000,
|
||||
currentSecond = 0,
|
||||
valuesGenerated = 0,
|
||||
currentValue = 0,
|
||||
interval;
|
||||
|
||||
interval = this.$interval(function () {
|
||||
var value = {
|
||||
time: +new Date(Date.now()),
|
||||
eu: currentValue
|
||||
},
|
||||
series = new EnumeratedTelemetrySeries([value]);
|
||||
|
||||
valuesGenerated += 1;
|
||||
if ((valuesGenerated % Math.pow(3, currentValue + 1)) === 0) {
|
||||
if (currentValue === 3) {
|
||||
currentValue = 0;
|
||||
} else {
|
||||
currentValue += 1;
|
||||
}
|
||||
}
|
||||
|
||||
callback(series);
|
||||
}, valueEvery);
|
||||
|
||||
return function () {
|
||||
this.$interval.cancel(interval);
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
EnumeratedTelemetryProvider.prototype.subscribe = function (
|
||||
callback,
|
||||
requests
|
||||
) {
|
||||
var validRequests = requests.filter(this.matchRequest),
|
||||
unsubscribes = validRequests.map(function (request) {
|
||||
var cb = function (series) {
|
||||
var telem = {},
|
||||
response = {
|
||||
'example.telemetry.enumerated': telem
|
||||
};
|
||||
telem[request.key] = series;
|
||||
callback(response);
|
||||
}
|
||||
return this.makeTelemetryEmitter(request, cb);
|
||||
}, this)
|
||||
return function () {
|
||||
unsubscribes.forEach(function(unsubscribe) {
|
||||
unsubscribe();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return EnumeratedTelemetryProvider;
|
||||
}
|
||||
);
|
||||
@@ -32,7 +32,7 @@ define([
|
||||
|
||||
legacyRegistry.register("example/eventGenerator", {
|
||||
"name": "Event Message Generator",
|
||||
"description": "Example of a component that produces event data.",
|
||||
"description": "For development use. Creates sample event message data that mimics a live data stream.",
|
||||
"extensions": {
|
||||
"components": [
|
||||
{
|
||||
@@ -49,8 +49,9 @@ define([
|
||||
{
|
||||
"key": "eventGenerator",
|
||||
"name": "Event Message Generator",
|
||||
"glyph": "f",
|
||||
"description": "An event message generator",
|
||||
"glyph": "\u0066",
|
||||
"description": "For development use. Creates sample event message data that mimics a live data stream.",
|
||||
"priority": 10,
|
||||
"features": "creation",
|
||||
"model": {
|
||||
"telemetry": {}
|
||||
|
||||
@@ -36,7 +36,7 @@ define([
|
||||
|
||||
legacyRegistry.register("example/generator", {
|
||||
"name": "Sine Wave Generator",
|
||||
"description": "Example of a component that produces dataa.",
|
||||
"description": "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||
"extensions": {
|
||||
"components": [
|
||||
{
|
||||
@@ -86,8 +86,9 @@ define([
|
||||
{
|
||||
"key": "generator",
|
||||
"name": "Sine Wave Generator",
|
||||
"glyph": "T",
|
||||
"description": "A sine wave generator",
|
||||
"glyph": "\u0054",
|
||||
"description": "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||
"priority": 10,
|
||||
"features": "creation",
|
||||
"model": {
|
||||
"telemetry": {
|
||||
@@ -126,7 +127,7 @@ define([
|
||||
{
|
||||
"name": "Period",
|
||||
"control": "textfield",
|
||||
"cssclass": "l-small l-numeric",
|
||||
"cssclass": "l-input-sm l-numeric",
|
||||
"key": "period",
|
||||
"required": true,
|
||||
"property": [
|
||||
|
||||
@@ -49,8 +49,10 @@ define([
|
||||
{
|
||||
"key": "imagery",
|
||||
"name": "Example Imagery",
|
||||
"glyph": "T",
|
||||
"glyph": "\u00e3",
|
||||
"features": "creation",
|
||||
"description": "For development use. Creates example imagery data that mimics a live imagery stream.",
|
||||
"priority": 10,
|
||||
"model": {
|
||||
"telemetry": {}
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@ define([
|
||||
{
|
||||
"name": "Measurement",
|
||||
"key": "msl.measurement",
|
||||
"glyph": "T",
|
||||
"glyph": "\u0054",
|
||||
"model": {"telemetry": {}},
|
||||
"telemetry": {
|
||||
"source": "rems.source",
|
||||
|
||||
@@ -80,9 +80,10 @@ define([
|
||||
"types": [
|
||||
{
|
||||
"key": "plot",
|
||||
"name": "Telemetry Plot",
|
||||
"glyph": "t",
|
||||
"description": "A plot for displaying telemetry",
|
||||
"name": "Example Telemetry Plot",
|
||||
"glyph": "\u0074",
|
||||
"description": "For development use. A plot for displaying telemetry.",
|
||||
"priority": 10,
|
||||
"delegates": [
|
||||
"telemetry"
|
||||
],
|
||||
|
||||
6
main.js
6
main.js
@@ -93,9 +93,7 @@ define([
|
||||
|
||||
'./example/imagery/bundle',
|
||||
'./example/eventGenerator/bundle',
|
||||
'./example/generator/bundle',
|
||||
'./example/enumeratedTelemetry/bundle'
|
||||
|
||||
'./example/generator/bundle'
|
||||
], function (Main, legacyRegistry) {
|
||||
'use strict';
|
||||
|
||||
@@ -105,4 +103,4 @@ define([
|
||||
return new Main().run(legacyRegistry);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "openmctweb",
|
||||
"version": "0.9.3-SNAPSHOT",
|
||||
"description": "The Open MCT Web core platform",
|
||||
"version": "0.10.0-SNAPSHOT",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {
|
||||
"express": "^4.13.1",
|
||||
"minimist": "^1.1.1",
|
||||
@@ -38,7 +38,7 @@
|
||||
"moment": "^2.11.1",
|
||||
"node-bourbon": "^4.2.3",
|
||||
"phantomjs-prebuilt": "^2.1.0",
|
||||
"requirejs": "^2.1.17",
|
||||
"requirejs": "2.1.x",
|
||||
"split": "^1.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -44,22 +44,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="holder l-flex-col flex-elem grows l-object-wrapper">
|
||||
<div ng-if="isEditable" class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
|
||||
<!-- Toolbar and Save/Cancel buttons -->
|
||||
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
|
||||
<mct-representation key="'edit-action-buttons'"
|
||||
mct-object="domainObject"
|
||||
class='flex-elem conclude-editing'>
|
||||
</mct-representation>
|
||||
|
||||
</div>
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="abs flex-elem grows object-holder-main scroll"
|
||||
toolbar="toolbar">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<div ng-if="!isEditable" class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
|
||||
<div class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
|
||||
<!-- Toolbar and Save/Cancel buttons -->
|
||||
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
|
||||
<mct-representation key="'edit-action-buttons'"
|
||||
|
||||
@@ -93,27 +93,23 @@ define(
|
||||
return wizard.populateObjectFromInput(formValue, newObject);
|
||||
}
|
||||
|
||||
function addToParent (populatedObject) {
|
||||
parentObject.getCapability('composition').add(populatedObject);
|
||||
return parentObject.getCapability('persistence').persist().then(function(){
|
||||
return parentObject;
|
||||
});
|
||||
function persistAndReturn(domainObject) {
|
||||
return domainObject.getCapability('persistence')
|
||||
.persist()
|
||||
.then(function () {
|
||||
return domainObject;
|
||||
});
|
||||
}
|
||||
|
||||
function save(object) {
|
||||
/*
|
||||
It's necessary to persist the new sub-object in order
|
||||
that it can be retrieved for composition in the parent.
|
||||
Future refactoring that allows temporary objects to be
|
||||
retrieved from object services will make this unnecessary.
|
||||
*/
|
||||
return object.getCapability('editor').save(true);
|
||||
function addToParent (populatedObject) {
|
||||
parentObject.getCapability('composition').add(populatedObject);
|
||||
return persistAndReturn(parentObject);
|
||||
}
|
||||
|
||||
return this.dialogService
|
||||
.getUserInput(wizard.getFormStructure(false), wizard.getInitialFormValue())
|
||||
.then(populateObjectFromInput)
|
||||
.then(save)
|
||||
.then(persistAndReturn)
|
||||
.then(addToParent);
|
||||
|
||||
};
|
||||
|
||||
@@ -34,6 +34,8 @@ define([
|
||||
"./src/actions/SaveAction",
|
||||
"./src/actions/CancelAction",
|
||||
"./src/policies/EditActionPolicy",
|
||||
"./src/policies/EditableLinkPolicy",
|
||||
"./src/policies/EditableMovePolicy",
|
||||
"./src/policies/EditNavigationPolicy",
|
||||
"./src/representers/EditRepresenter",
|
||||
"./src/representers/EditToolbarRepresenter",
|
||||
@@ -56,6 +58,8 @@ define([
|
||||
SaveAction,
|
||||
CancelAction,
|
||||
EditActionPolicy,
|
||||
EditableLinkPolicy,
|
||||
EditableMovePolicy,
|
||||
EditNavigationPolicy,
|
||||
EditRepresenter,
|
||||
EditToolbarRepresenter,
|
||||
@@ -187,6 +191,14 @@ define([
|
||||
"category": "action",
|
||||
"implementation": EditActionPolicy
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": EditableMovePolicy
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": EditableLinkPolicy
|
||||
},
|
||||
{
|
||||
"category": "navigation",
|
||||
"message": "There are unsaved changes.",
|
||||
|
||||
52
platform/commonUI/edit/src/policies/EditableLinkPolicy.js
Normal file
52
platform/commonUI/edit/src/policies/EditableLinkPolicy.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([], function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Policy suppressing links when the linked-to domain object is in
|
||||
* edit mode. Domain objects being edited may not have been persisted,
|
||||
* so creating links to these can result in inconsistent state.
|
||||
*
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Policy.<View, DomainObject>}
|
||||
*/
|
||||
function EditableLinkPolicy() {
|
||||
}
|
||||
|
||||
EditableLinkPolicy.prototype.allow = function (action, context) {
|
||||
var key = action.getMetadata().key;
|
||||
|
||||
if (key === 'link') {
|
||||
return !((context.selectedObject || context.domainObject)
|
||||
.hasCapability('editor'));
|
||||
}
|
||||
|
||||
// Like all policies, allow by default.
|
||||
return true;
|
||||
};
|
||||
|
||||
return EditableLinkPolicy;
|
||||
});
|
||||
51
platform/commonUI/edit/src/policies/EditableMovePolicy.js
Normal file
51
platform/commonUI/edit/src/policies/EditableMovePolicy.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([], function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Policy suppressing move actions among editable and non-editable
|
||||
* domain objects.
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Policy.<View, DomainObject>}
|
||||
*/
|
||||
function EditableMovePolicy() {
|
||||
}
|
||||
|
||||
EditableMovePolicy.prototype.allow = function (action, context) {
|
||||
var domainObject = context.domainObject,
|
||||
selectedObject = context.selectedObject,
|
||||
key = action.getMetadata().key;
|
||||
|
||||
if (key === 'move' && domainObject.hasCapability('editor')) {
|
||||
return !!selectedObject && selectedObject.hasCapability('editor');
|
||||
}
|
||||
|
||||
// Like all policies, allow by default.
|
||||
return true;
|
||||
};
|
||||
|
||||
return EditableMovePolicy;
|
||||
});
|
||||
@@ -118,8 +118,6 @@ define(
|
||||
// Track the represented object
|
||||
this.domainObject = representedObject;
|
||||
|
||||
this.scope.isEditable = representedObject.getCapability('status').get('editing');
|
||||
|
||||
// Ensure existing watches are released
|
||||
this.destroy();
|
||||
|
||||
|
||||
@@ -28,9 +28,10 @@ define([
|
||||
) {
|
||||
"use strict";
|
||||
|
||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss",
|
||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
||||
DATE_FORMATS = [
|
||||
DATE_FORMAT,
|
||||
"YYYY-MM-DD HH:mm:ss",
|
||||
"YYYY-MM-DD HH:mm",
|
||||
"YYYY-MM-DD"
|
||||
];
|
||||
@@ -48,7 +49,7 @@ define([
|
||||
}
|
||||
|
||||
UTCTimeFormat.prototype.format = function (value) {
|
||||
return moment.utc(value).format(DATE_FORMAT);
|
||||
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.parse = function (text) {
|
||||
|
||||
@@ -40,6 +40,12 @@ define(
|
||||
expect(moment.utc(formatted).valueOf()).toEqual(timestamp);
|
||||
});
|
||||
|
||||
it("displays with millisecond precision", function () {
|
||||
var timestamp = 12345670789,
|
||||
formatted = format.format(timestamp);
|
||||
expect(moment.utc(formatted).valueOf()).toEqual(timestamp);
|
||||
});
|
||||
|
||||
it("validates time inputs", function () {
|
||||
expect(format.validate("1977-05-25 11:21:22")).toBe(true);
|
||||
expect(format.validate("garbage text")).toBe(false);
|
||||
|
||||
@@ -177,10 +177,6 @@ define([
|
||||
{
|
||||
"stylesheetUrl": "css/normalize.min.css",
|
||||
"priority": "mandatory"
|
||||
},
|
||||
{
|
||||
"stylesheetUrl": "css/reset.css",
|
||||
"priority": "mandatory"
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
@@ -523,16 +519,6 @@ define([
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"name": "Modernizr",
|
||||
"version": "2.6.2",
|
||||
"description": "Browser/device capability finding",
|
||||
"author": "Faruk Ateş",
|
||||
"website": "http://modernizr.com",
|
||||
"copyright": "Copyright (c) 2009–2015",
|
||||
"license": "license-mit",
|
||||
"link": "http://modernizr.com/license/"
|
||||
},
|
||||
{
|
||||
"name": "Normalize.css",
|
||||
"version": "1.1.2",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -80,7 +80,7 @@
|
||||
<glyph unicode="" glyph-name="icon-datatable" d="M1024 768c0-106.039-229.23-192-512-192s-512 85.961-512 192c0 106.039 229.23 192 512 192s512-85.961 512-192zM512 448c-282.8 0-512 86-512 192v-512c0-106 229.2-192 512-192s512 86 512 192v512c0-106-229.2-192-512-192zM896 385v-256c-36.6-15.6-79.8-28.8-128-39.4v256c48.2 10.6 91.4 23.8 128 39.4zM256 345.6v-256c-48.2 10.4-91.4 23.8-128 39.4v256c36.6-15.6 79.8-28.8 128-39.4zM384 70v256c41-4 83.8-6 128-6s87 2.2 128 6v-256c-41-4-83.8-6-128-6s-87 2.2-128 6z" />
|
||||
<glyph unicode="" glyph-name="icon-tabular-scrolling" d="M64 960c-35.2 0-64-28.8-64-64v-192h448v256h-384zM1024 704v192c0 35.2-28.8 64-64 64h-384v-256h448zM0 576v-192c0-35.2 28.8-64 64-64h384v256h-448zM960 320c35.2 0 64 28.8 64 64v192h-448v-256h384zM512-64l-256 256h512z" />
|
||||
<glyph unicode="" glyph-name="icon-alert-triangle" d="M998.208 111.136l-422.702 739.728c-34.928 61.124-92.084 61.124-127.012 0l-422.702-739.728c-34.928-61.126-5.906-111.136 64.494-111.136h843.428c70.4 0 99.422 50.010 64.494 111.136zM512 128c-35.2 0-64 28.8-64 64s28.8 64 64 64 64-28.8 64-64c0-35.2-28.8-64-64-64zM627.448 577.242l-38.898-194.486c-6.902-34.516-41.35-62.756-76.55-62.756s-69.648 28.24-76.552 62.758l-38.898 194.486c-6.902 34.516 16.25 62.756 51.45 62.756h128c35.2 0 58.352-28.24 51.448-62.758z" />
|
||||
<glyph unicode="" glyph-name="icon-tabular" d="M0 896v-192h448v256h-384c-35.2 0-64-28.8-64-64zM960 960h-384v-256h448v192c0 35.2-28.8 64-64 64zM576 576h448v-256h-448v256zM0 576h448v-256h-448v256zM0 0c0-35.2 28.8-64 64-64h384v256h-448v-192zM576-64h384c35.2 0 64 28.8 64 64v192h-448v-256z" />
|
||||
<glyph unicode="" glyph-name="icon-tabular" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM640 512h-256v192h256v-192zM384 448h256v-192h-256v192zM320 256h-256v192h256v-192zM320 704v-192h-256v192h256zM128 0c-17 0-33 6.6-45.2 18.8s-18.8 28.2-18.8 45.2v128h256v-192h-192zM384 0v192h256v-192h-256zM960 64c0-17-6.6-33-18.8-45.2s-28.2-18.8-45.2-18.8h-192v192h256v-128zM960 256h-256v192h256v-192zM960 512h-256v192h256v-192z" />
|
||||
<glyph unicode="" glyph-name="icon-calendar" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM640 512h-256v192h256v-192zM384 448h256v-192h-256v192zM320 256h-256v192h256v-192zM320 704v-192h-256v192h256zM128 0c-17 0-33 6.6-45.2 18.8s-18.8 28.2-18.8 45.2v128h256v-192h-192zM384 0v192h256v-192h-256zM960 64c0-17-6.6-33-18.8-45.2s-28.2-18.8-45.2-18.8h-192v192h256v-128zM960 256h-256v192h256v-192zM960 512h-256v192h256v-192z" />
|
||||
<glyph unicode="" glyph-name="icon-paint-bucket" d="M544 736v-224c0-88.4-71.6-160-160-160s-160 71.6-160 160v97.2l-197.4-196.4c-50-50-12.4-215.2 112.4-340s290-162.4 340-112.4l417 423.6-352 352zM896-64c70.6 0 128 57.4 128 128 0 108.6-128 192-128 192s-128-83.4-128-192c0-70.6 57.4-128 128-128zM384 448c-35.4 0-64 28.6-64 64v384c0 35.4 28.6 64 64 64s64-28.6 64-64v-384c0-35.4-28.6-64-64-64z" />
|
||||
<glyph unicode="" glyph-name="icon-x-in-circle" d="M512 960c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM832 256l-128-128-192 192-192-192-128 128 192 192-192 192 128 128 192-192 192 192 128-128-192-192 192-192z" />
|
||||
@@ -94,8 +94,11 @@
|
||||
<glyph unicode="" glyph-name="icon-collapse-pane-right" d="M768 960h256v-1024h-256c-105.6 0-192 86.4-192 192v640c0 105.6 86.4 192 192 192zM512 640l-512-320v640z" />
|
||||
<glyph unicode="" glyph-name="icon-eye-open" d="M512 896c-261 0-480.6-195.4-512-448 31.4-252.6 251-448 512-448s480.6 195.4 512 448c-31.4 252.6-251 448-512 448zM768.2 225.4c-71.4-62.8-162.8-97.4-257.6-97.4s-186.2 34.6-257.6 97.4c-66.6 58.6-110.6 137.2-125 222.6 0 0 0 0.2 0 0.2 76.8 154 220.8 257.6 384 257.6s307.2-103.8 384-257.6c0 0 0-0.2 0-0.2-14.4-85.4-61.2-164-127.8-222.6zM512 672c-123.8 0-224-100.2-224-224s100.2-224 224-224 224 100.2 224 224-100.2 224-224 224z" />
|
||||
<glyph unicode="" glyph-name="icon-eye-open-no-gleam" d="M512 896c-261 0-480.6-195.4-512-448 31.4-252.6 251-448 512-448s480.6 195.4 512 448c-31.4 252.6-251 448-512 448zM768.2 225.4c-71.4-62.8-162.8-97.4-257.6-97.4s-186.2 34.6-257.6 97.4c-66.6 58.6-110.6 137.2-125 222.6 0 0 0 0.2 0 0.2 76.8 154 220.8 257.6 384 257.6s307.2-103.8 384-257.6c0 0 0-0.2 0-0.2-14.4-85.4-61.2-164-127.8-222.6zM512 672c-123.8 0-224-100.2-224-224s100.2-224 224-224 224 100.2 224 224-100.2 224-224 224zM576 416c-53 0-96 43-96 96s43 96 96 96 96-43 96-96c0-53-43-96-96-96z" />
|
||||
<glyph unicode="" glyph-name="icon-topic" d="M546.4 528.8l32 24c31.6 23.8 91.6 23.8 123.2 0l32-24c10.8-8 22.2-15.2 34.4-21.4v201.2c-38 19.6-82.2 30-128 30-60.4 0-118.2-18.2-162.4-51.4l-32-24c-31.6-23.8-91.6-23.8-123.2 0l-32 24c-10.8 8-22.2 15.2-34.4 21.4v-201.2c38-19.6 82.2-30 128-30 60.4 0 118.2 18.2 162.4 51.4zM640 418.6c-60.4 0-118.2-18.2-162.4-51.4l-32-24c-31.6-23.8-91.6-23.8-123.2 0l-32 24c-10.8 8-22.2 15.2-34.4 21.4v-201.2c38-19.6 82.2-30 128-30 60.4 0 118.2 18.2 162.4 51.4l32 24c31.6 23.8 91.6 23.8 123.2 0l32-24c10.8-8 22.2-15.2 34.4-21.4v201.2c-38 19.6-82.2 30-128 30zM832 960h-128v-192h127.6c0.2 0 0.2-0.2 0.4-0.4v-639.4c0-0.2-0.2-0.2-0.4-0.4h-127.6v-192h128c105.6 0 192 86.4 192 192v640.2c0 105.6-86.4 192-192 192zM320 128h-127.6c-0.2 0-0.2 0.2-0.4 0.4v639.4c0 0.2 0.2 0.2 0.4 0.4h127.6v191.8h-128c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h128v192z" />
|
||||
<glyph unicode="" glyph-name="icon-session" d="M923 521.8l-151 100.6c-36 24-103.8 24-139.8 0l-151-100.6c-44.6-29.8-102.6-46.2-163-46.2s-118.4 16.4-163 46.2l-151.4 100.6c-1.8 1.2-3.8 2.4-5.8 3.6v-208c36.6-7.4 70.6-20.8 99-39.8l151-100.6c36-24 103.8-24 139.8 0l151 100.6c44.6 29.8 102.6 46.2 163 46.2s118.4-16.4 163-46.2l151-100.6c1.8-1.2 3.8-2.4 5.8-3.6v208c-36.2 7.2-70.2 20.8-98.6 39.8zM923 137.8l-151 100.6c-36 24-103.8 24-139.8 0l-151-100.6c-44.6-29.8-102.6-46.2-163-46.2s-118.4 16.4-163 46.2l-151.4 100.6c-1.8 1.2-3.8 2.4-5.8 3.6v-112c0-105.6 86.4-192 192-192h640c94.8 0 174.2 69.8 189.4 160.4-35.6 7.4-68.6 20.8-96.4 39.4zM97 762.2l151-100.6c36-24 103.8-24 139.8 0l151 100.6c44.8 29.8 102.6 46.2 163.2 46.2s118.4-16.4 163-46.2l151-100.6c1.8-1.2 3.8-2.4 5.8-3.6v112c0 105.6-86.4 192-192 192h-639.8c-94.8 0-174.2-69.8-189.4-160.4 35.6-7.4 68.6-20.8 96.4-39.4z" />
|
||||
<glyph unicode="" glyph-name="icon-topic" d="M454.36 483.36l86.3 86.3c9.088 8.965 21.577 14.502 35.36 14.502s26.272-5.537 35.366-14.507l86.294-86.294c19.328-19.358 42.832-34.541 69.047-44.082l1.313 171.722-57.64 57.64c-34.407 34.33-81.9 55.558-134.35 55.558s-99.943-21.228-134.354-55.562l-86.296-86.297c-9.088-8.965-21.577-14.502-35.36-14.502s-26.272 5.537-35.366 14.507l-28.674 28.654v-172.14c19.045-7.022 41.040-11.084 63.984-11.084 52.463 0 99.966 21.239 134.379 55.587zM505.64 412.64l-86.3-86.3c-9.088-8.965-21.577-14.502-35.36-14.502s-26.272 5.537-35.366 14.507l-86.294 86.294c-2 2-4.2 4-6.36 6v-197.36c33.664-30.72 78.65-49.537 128.031-49.537 52.44 0 99.923 21.22 134.333 55.541l86.296 86.296c9.088 8.965 21.577 14.502 35.36 14.502s26.272-5.537 35.366-14.507l86.294-86.294c2-2 4.2-4 6.36-6v197.36c-33.664 30.72-78.65 49.537-128.031 49.537-52.44 0-99.923-21.22-134.333-55.541zM832 960h-128v-192h127.66l0.34-0.34v-639.32l-0.34-0.34h-127.66v-192h128c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM320 128h-127.66l-0.34 0.34v639.32l0.34 0.34h127.66v192h-128c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h128v192z" />
|
||||
<glyph unicode="" glyph-name="icon-session" d="M635.6 435.6c6.6-4.2 13.2-8.6 19.2-13.6l120.4-96.4c29.6-23.8 83.8-23.8 113.4 0l135.2 108c0.2 4.8 0.2 9.4 0.2 14.2 0 52.2-7.8 102.4-22.2 149.8l-154.8-123.6c-58.2-46.6-140.2-59.2-211.4-38.4zM248.6 325.8l120.4 96.4c58 46.4 140 59.2 211.2 38.4-6.6 4.2-13.2 8.6-19.2 13.6l-120.4 96.4c-29.6 23.8-83.8 23.8-113.4 0l-120.2-96.6c-40-32-91.4-48-143-48-21.6 0-43 2.8-63.8 8.4 0-0.6 0-1.2 0-1.6 5-3.4 10-6.8 14.6-10.6l120.4-96.4c29.8-23.8 83.8-23.8 113.4 0zM120.6 581.8l120.4 96.4c80.2 64.2 205.6 64.2 285.8 0l120.4-96.4c29.6-23.8 83.8-23.8 113.4 0l181 144.8c-91.2 140.4-249.6 233.4-429.6 233.4-238.6 0-439.2-163.2-496-384.2 30.8-17.6 77.8-15.6 104.6 6zM689 218l-120.4 96.4c-29.6 23.8-83.8 23.8-113.4 0l-120.2-96.4c-40-32-91.4-48-143-48-47.8 0-95.4 13.8-134.2 41.4 85.6-163.6 256.8-275.4 454.2-275.4s368.6 111.8 454.2 275.4c-80.4-57.4-199.8-55.2-277.2 6.6z" />
|
||||
<glyph unicode="" glyph-name="icon-bullet" d="M832 208c0-44-36-80-80-80h-480c-44 0-80 36-80 80v480c0 44 36 80 80 80h480c44 0 80-36 80-80v-480z" />
|
||||
<glyph unicode="" glyph-name="icon-tabular-realtime" d="M896 960h-768c-70.606-0.215-127.785-57.394-128-127.979v-768.021c0.215-70.606 57.394-127.785 127.979-128h768.021c70.606 0.215 127.785 57.394 128 127.979v768.021c-0.215 70.606-57.394 127.785-127.979 128zM448 668l25.060-25.32c7.916-7.922 18.856-12.822 30.94-12.822s23.024 4.9 30.94 12.822l75.5 76.3c29.97 30.338 71.571 49.128 117.56 49.128s87.59-18.79 117.544-49.112l50.456-50.997v-152.2c-24.111 8.83-44.678 22.255-61.542 39.342l-75.518 76.318c-7.916 7.922-18.856 12.822-30.94 12.822s-23.024-4.9-30.94-12.822l-75.5-76.3c-29.971-30.343-71.575-49.137-117.568-49.137-20.084 0-39.331 3.584-57.137 10.146l1.145 151.831zM320 0h-192c-35.26 0.214-63.786 28.74-64 63.98v128.020h256v-192zM320 256h-256v192h256v-192zM320 512h-256v192h256v-192zM640 0h-256v192h256v-192zM448 323.38v174.5c1.88-1.74 3.74-3.5 5.56-5.34l75.5-76.3c7.916-7.922 18.856-12.822 30.94-12.822s23.024 4.9 30.94 12.822l75.5 76.3c29.966 30.333 71.56 49.119 117.542 49.119 43.28 0 82.673-16.644 112.128-43.879l-0.11-174.399c-1.88 1.74-3.74 3.5-5.56 5.34l-75.5 76.3c-7.916 7.922-18.856 12.822-30.94 12.822s-23.024-4.9-30.94-12.822l-75.5-76.3c-29.966-30.333-71.56-49.119-117.542-49.119-43.28 0-82.673 16.644-112.128 43.879zM960 64c-0.214-35.26-28.74-63.786-63.98-64h-192.020v192h256v-128z" />
|
||||
<glyph unicode="" glyph-name="icon-tabular-lad" d="M896 960h-768c-70.606-0.215-127.785-57.394-128-127.979v-768.021c0.215-70.606 57.394-127.785 127.979-128h768.021c70.606 0.215 127.785 57.394 128 127.979v768.021c-0.215 70.606-57.394 127.785-127.979 128zM64 704h256v-192h-256v192zM64 448h256v-192h-256v192zM128 0c-35.26 0.214-63.786 28.74-64 63.98v128.020h256v-192h-192zM384 0v192h256v-192h-256zM960 64c-0.214-35.26-28.74-63.786-63.98-64h-192.020v192h256v-128zM960 448v-192h-576v192h64v64h-64v192h576v-192h-64v-64h64zM782.32 412.62l-110.32 55.16v172.22c0 17.673-14.327 32-32 32s-32-14.327-32-32v-211.78l145.68-72.84c4.172-2.133 9.1-3.383 14.32-3.383 17.675 0 32.003 14.328 32.003 32.003 0 12.454-7.114 23.247-17.501 28.536z" />
|
||||
<glyph unicode="" glyph-name="icon-tabular-lad-set" d="M128 192v576c-70.606-0.215-127.785-57.394-128-127.979v-576.021c0.215-70.606 57.394-127.785 127.979-128h576.021c70.606 0.215 127.785 57.394 128 127.979l-576 0.021c-70.606 0.215-127.785 57.394-128 127.979zM896 960h-576c-70.606-0.215-127.785-57.394-128-127.979v-576.021c0.215-70.606 57.394-127.785 127.979-128h576.021c70.606 0.215 127.785 57.394 128 127.979v576.021c-0.215 70.606-57.394 127.785-127.979 128zM256 768h192v-128h-192v128zM256 576h192v-192h-192v192zM320 192c-35.26 0.214-63.786 28.74-64 63.98v64.020h192v-128h-128zM512 192v128h192v-128h-192zM960 256c-0.214-35.26-28.74-63.786-63.98-64h-128.020v128h192v-64zM960 384h-448v384h448v-384zM832 480c0.002 0 0.005 0 0.007 0 17.673 0 32 14.327 32 32 0 14.055-9.062 25.994-21.662 30.293l-74.345 24.767v104.94c0 17.673-14.327 32-32 32s-32-14.327-32-32v-151.060l117.88-39.3c3.018-1.040 6.495-1.64 10.113-1.64 0.003 0 0.005 0 0.008 0z" />
|
||||
<glyph unicode="" glyph-name="icon-x" d="M384 448l-365.332-365.332c-24.89-24.89-24.89-65.62 0-90.51l37.49-37.49c24.89-24.89 65.62-24.89 90.51 0 0 0 365.332 365.332 365.332 365.332l365.332-365.332c24.89-24.89 65.62-24.89 90.51 0l37.49 37.49c24.89 24.89 24.89 65.62 0 90.51l-365.332 365.332c0 0 365.332 365.332 365.332 365.332 24.89 24.89 24.89 65.62 0 90.51l-37.49 37.49c-24.89 24.89-65.62 24.89-90.51 0 0 0-365.332-365.332-365.332-365.332l-365.332 365.332c-24.89 24.89-65.62 24.89-90.51 0l-37.49-37.49c-24.89-24.89-24.89-65.62 0-90.51 0 0 365.332-365.332 365.332-365.332z" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 35 KiB |
Binary file not shown.
Binary file not shown.
@@ -98,6 +98,11 @@ $bubbleMaxW: 300px;
|
||||
$reqSymbolW: 15px;
|
||||
$reqSymbolM: $interiorMargin * 2;
|
||||
$reqSymbolFontSize: 0.7em;
|
||||
// Wait Spinner Defaults
|
||||
$waitSpinnerD: 32px;
|
||||
$waitSpinnerTreeD: 20px;
|
||||
$waitSpinnerBorderW: 5px;
|
||||
$waitSpinnerTreeBorderW: 4px;
|
||||
|
||||
/************************** CONTROLS */
|
||||
$controlDisabledOpacity: 0.3;
|
||||
|
||||
@@ -53,7 +53,6 @@ body, html {
|
||||
font-weight: 200;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
em {
|
||||
@@ -85,6 +84,8 @@ p {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
}
|
||||
|
||||
ol, ul { padding-left: 0; }
|
||||
|
||||
mct-container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
|
||||
/********************************* FORMS */
|
||||
@import "forms/elems";
|
||||
@import "forms/selects";
|
||||
@import "forms/channel-selector";
|
||||
@import "forms/datetime";
|
||||
@import "forms/validation";
|
||||
|
||||
@@ -35,15 +35,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
mct-representation {
|
||||
mct-representation,
|
||||
.rep-object-label {
|
||||
&.s-status-pending {
|
||||
.t-object-label {
|
||||
.t-item-icon {
|
||||
&:before {
|
||||
$spinBW: 4px;
|
||||
@include spinner($spinBW);
|
||||
@include spinner($waitSpinnerTreeBorderW, $colorLoadingFg);
|
||||
content: "";
|
||||
padding: 30%;
|
||||
height: $waitSpinnerTreeD; width: $waitSpinnerTreeD;
|
||||
}
|
||||
.t-item-icon-glyph {
|
||||
display: none;
|
||||
@@ -57,7 +57,10 @@ mct-representation {
|
||||
}
|
||||
}
|
||||
|
||||
.selected mct-representation.s-status-pending .t-object-label .t-item-icon:before {
|
||||
border-color: rgba($colorItemTreeSelectedFg, 0.25) !important;
|
||||
border-top-color: rgba($colorItemTreeSelectedFg, 1.0) !important;
|
||||
.selected mct-representation,
|
||||
.selected .rep-object-label {
|
||||
.s-status-pending .t-object-label .t-item-icon:before {
|
||||
border-color: rgba($colorItemTreeSelectedFg, 0.25) !important;
|
||||
border-top-color: rgba($colorItemTreeSelectedFg, 1.0) !important;
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,12 @@
|
||||
|
||||
.l-composite-control {
|
||||
vertical-align: middle;
|
||||
&:not(.l-inline) {
|
||||
margin-bottom: $interiorMargin;
|
||||
}
|
||||
&.l-inline {
|
||||
display: inline-block;
|
||||
}
|
||||
&.l-checkbox {
|
||||
.composite-control-label {
|
||||
line-height: 18px;
|
||||
@@ -108,12 +114,14 @@
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
/******************************************************** CUSTOM CHECKBOXES */
|
||||
label.checkbox.custom,
|
||||
label.radio.custom {
|
||||
$bg: pullForward($colorBodyBg, 10%);
|
||||
$d: $formRowCtrlsH;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: 120%;
|
||||
margin-right: $interiorMargin * 4;
|
||||
padding-left: $d + $interiorMargin;
|
||||
position: relative;
|
||||
@@ -161,7 +169,40 @@ label.radio.custom {
|
||||
label.checkbox.custom input:checked ~ em:before { content: "\32"; }
|
||||
label.radio.custom input:checked ~ em:before { content: "\e619"; }
|
||||
|
||||
.s-menu-btn label.checkbox.custom {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.item .checkbox {
|
||||
&.checked label {
|
||||
box-shadow: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
label.form-control.checkbox {
|
||||
input {
|
||||
margin-right: $interiorMargin;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** INPUTS */
|
||||
input[type="text"],
|
||||
input[type="search"] {
|
||||
@include nice-input();
|
||||
&.numeric {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.l-input-lg input[type="text"] { width: 100% !important; }
|
||||
.l-input-med input[type="text"] { width: 200px !important; }
|
||||
.l-input-sm input[type="text"] { width: 50px !important; }
|
||||
.l-numeric input[type="text"] { text-align: right; }
|
||||
|
||||
.input-labeled {
|
||||
// Used in toolbar
|
||||
margin-left: $interiorMargin;
|
||||
label {
|
||||
display: inline-block;
|
||||
@@ -175,28 +216,36 @@ label.radio.custom input:checked ~ em:before { content: "\e619"; }
|
||||
}
|
||||
}
|
||||
|
||||
.s-menu-btn label.checkbox.custom {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.item .checkbox {
|
||||
&.checked label {
|
||||
box-shadow: none;
|
||||
border-bottom: none;
|
||||
/******************************************************** SELECTS */
|
||||
.select {
|
||||
@include btnSubtle($colorSelectBg);
|
||||
@if $shdwBtns != none {
|
||||
margin: 0 0 2px 0; // Needed to avoid dropshadow from being clipped by parent containers
|
||||
}
|
||||
}
|
||||
|
||||
.context-available,
|
||||
.s-icon-btn {
|
||||
$c: $colorKey;
|
||||
color: $c;
|
||||
&:hover {
|
||||
color: lighten($c, 10%);
|
||||
padding: 0 $interiorMargin;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
line-height: $formInputH;
|
||||
select {
|
||||
@include appearance(none);
|
||||
box-sizing: border-box;
|
||||
background: none;
|
||||
color: $colorSelectFg;
|
||||
cursor: pointer;
|
||||
border: none !important;
|
||||
padding: 4px 25px 2px 0px;
|
||||
width: 130%;
|
||||
option {
|
||||
margin: $interiorMargin 0; // Firefox
|
||||
}
|
||||
}
|
||||
&:after {
|
||||
@include contextArrow();
|
||||
pointer-events: none;
|
||||
color: rgba($colorSelectFg, percentToDecimal($contrastInvokeMenuPercent));
|
||||
position: absolute;
|
||||
right: $interiorMargin; top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.view-switcher {
|
||||
@include trans-prop-nice-fade($controlFadeMs);
|
||||
}
|
||||
|
||||
/******************************************************** OBJECT-HEADER */
|
||||
@@ -330,7 +379,6 @@ body.desktop .object-header {
|
||||
}
|
||||
|
||||
/******************************************************** SLIDERS */
|
||||
|
||||
.slider {
|
||||
$knobH: 100%;
|
||||
.slot {
|
||||
@@ -424,7 +472,7 @@ body.desktop .object-header {
|
||||
border-top: 1px solid $colorInteriorBorder
|
||||
}
|
||||
.l-time-selects {
|
||||
line-height: $formInputH;
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,11 +526,31 @@ body.desktop .object-header {
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** BROWSER ELEMENTS */
|
||||
/******************************************************** TEXTAREA */
|
||||
textarea {
|
||||
@include nice-textarea($colorInputBg, $colorInputFg);
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/******************************************************** MISC */
|
||||
.context-available,
|
||||
.s-icon-btn {
|
||||
$c: $colorKey;
|
||||
color: $c;
|
||||
&:hover {
|
||||
color: lighten($c, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.view-switcher {
|
||||
@include trans-prop-nice-fade($controlFadeMs);
|
||||
}
|
||||
|
||||
/******************************************************** BROWSER ELEMENTS */
|
||||
body.desktop {
|
||||
::-webkit-scrollbar {
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: inset $scrollbarTrackShdw;
|
||||
background-color: $scrollbarTrackColorBg;
|
||||
@@ -491,15 +559,15 @@ body.desktop {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
$bg: $scrollbarThumbColor;
|
||||
$hc: $scrollbarThumbColorHov;
|
||||
$gr: 5%;
|
||||
@include background-image(linear-gradient(lighten($bg, $gr), $bg 20px));
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
&:hover {
|
||||
@include background-image(linear-gradient(lighten($hc, $gr), $hc 20px));
|
||||
}
|
||||
background: $scrollbarThumbColor;
|
||||
&:hover { background: $scrollbarThumbColorHov; }
|
||||
}
|
||||
|
||||
.overlay ::-webkit-scrollbar-thumb {
|
||||
$lr: 15%;
|
||||
background: $scrollbarThumbColorOverlay;
|
||||
&:hover { background: $scrollbarThumbColorOverlayHov; }
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
|
||||
@@ -51,9 +51,6 @@
|
||||
.title-label {
|
||||
font-size: 1rem;
|
||||
}
|
||||
//&:after {
|
||||
// color: rgba($colorInvokeMenu, 0.5);
|
||||
//}
|
||||
}
|
||||
|
||||
.menu {
|
||||
@@ -113,12 +110,13 @@
|
||||
|
||||
.menu,
|
||||
.context-menu,
|
||||
.super-menu {
|
||||
.super-menu,
|
||||
.s-menu-btn .menu {
|
||||
pointer-events: auto;
|
||||
ul li {
|
||||
//padding-left: 25px;
|
||||
a {
|
||||
color: $colorMenuFg;
|
||||
display: block;
|
||||
}
|
||||
.icon {
|
||||
color: $colorMenuIc;
|
||||
@@ -126,9 +124,6 @@
|
||||
.type-icon {
|
||||
left: $interiorMargin;
|
||||
}
|
||||
&:hover .icon {
|
||||
//color: lighten($colorMenuIc, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +141,7 @@
|
||||
height: $d;
|
||||
width: $d;
|
||||
&:before {
|
||||
font-size: 7px !important;// $d/2;
|
||||
font-size: 7px !important;
|
||||
height: $d;
|
||||
width: $d;
|
||||
line-height: $d;
|
||||
@@ -173,7 +168,6 @@
|
||||
.pane {
|
||||
box-sizing: border-box;
|
||||
&.left {
|
||||
//@include test();
|
||||
border-right: 1px solid pullForward($colorMenuBg, 10%);
|
||||
left: 0;
|
||||
padding-right: $interiorMargin;
|
||||
@@ -190,7 +184,6 @@
|
||||
}
|
||||
}
|
||||
&.right {
|
||||
//@include test(red);
|
||||
left: auto;
|
||||
right: 0;
|
||||
padding: $interiorMargin * 5;
|
||||
@@ -216,7 +209,6 @@
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
&.description {
|
||||
//color: lighten($colorMenuBg, 30%);
|
||||
color: $colorCreateMenuText;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.5em;
|
||||
@@ -258,4 +250,4 @@
|
||||
left: auto;
|
||||
right: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
>.controls {
|
||||
box-sizing: border-box;
|
||||
font-size: 0.8rem;
|
||||
min-height: $formInputH;
|
||||
}
|
||||
|
||||
>.label {
|
||||
@@ -88,9 +87,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.field-hints {
|
||||
color: darken($colorBodyFg, 20%);
|
||||
}
|
||||
.hint, .field-hints { color: $colorFieldHint; }
|
||||
|
||||
.selector-list {
|
||||
// Used in create overlay to display tree view
|
||||
@@ -131,6 +128,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.l-controls-under.l-flex-row {
|
||||
// Change to use column layout
|
||||
@include flex-direction(column);
|
||||
.flex-elem {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
}
|
||||
}
|
||||
|
||||
.no-validate {
|
||||
.form .form-row >.label {
|
||||
padding-right: 0;
|
||||
@@ -140,13 +145,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
label.form-control.checkbox {
|
||||
input {
|
||||
margin-right: $interiorMargin;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.hint,
|
||||
.s-hint {
|
||||
font-size: 0.9em;
|
||||
@@ -166,31 +164,4 @@ label.form-control.checkbox {
|
||||
color: lighten($colorFormInvalid, 30%);
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="search"] {
|
||||
@include nice-input();
|
||||
&.numeric {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.l-input-med input[type="text"] {
|
||||
width: 200px !important;
|
||||
}
|
||||
|
||||
.l-input-sm input[type="text"] {
|
||||
width: 50px !important;
|
||||
}
|
||||
|
||||
.l-numeric input[type="text"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@include nice-textarea($colorInputBg, $colorInputFg);
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -23,9 +23,13 @@
|
||||
> .label {
|
||||
padding-right: $reqSymbolM; // Keep room for validation element
|
||||
&::after {
|
||||
float: right;
|
||||
position: absolute;
|
||||
right: $interiorMargin;
|
||||
font-family: symbolsfont;
|
||||
font-size: $reqSymbolFontSize;
|
||||
height: 100%;
|
||||
line-height: 200%;
|
||||
|
||||
}
|
||||
}
|
||||
&.invalid,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
|
||||
}
|
||||
|
||||
@mixin spinner($b: 5px, $c: $colorKey) {
|
||||
@mixin spinner($b: 5px, $c: $colorKey) {
|
||||
@include transform-origin(center);
|
||||
@include animation-name(rotation-centered);
|
||||
@include animation-duration(0.5s);
|
||||
@@ -46,10 +46,7 @@
|
||||
}
|
||||
|
||||
.wait-spinner {
|
||||
$d: 5%;
|
||||
@include spinner(0.5em, $colorKey);
|
||||
height: auto; width: auto;
|
||||
padding: $d; // Will size object based on parent container WIDTH
|
||||
@include spinner($waitSpinnerBorderW, $colorKey);
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
&.inline {
|
||||
@@ -60,15 +57,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.treeview .wait-spinner {
|
||||
// Only used in subtree.html, which I don't think this is actually being used
|
||||
$d: 10px;
|
||||
height: $d; width: $d;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
top: 2px; left: 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
// Can be applied to any block element with height and width
|
||||
pointer-events: none;
|
||||
@@ -77,8 +65,8 @@
|
||||
content: '';
|
||||
}
|
||||
&:before {
|
||||
@include spinner(5px, $colorLoadingFg);
|
||||
padding: 5%;
|
||||
@include spinner($waitSpinnerBorderW, $colorLoadingFg);
|
||||
height: $waitSpinnerD; width: $waitSpinnerD;
|
||||
z-index: 10;
|
||||
}
|
||||
&:after {
|
||||
@@ -87,8 +75,22 @@
|
||||
display: block;
|
||||
z-index: 9;
|
||||
}
|
||||
&.tree-item:before {
|
||||
padding: $menuLineH / 4;
|
||||
border-width: 2px;
|
||||
&.tree-item.t-wait-node {
|
||||
$d: $waitSpinnerTreeD;
|
||||
$spinnerL: $treeVCW + $interiorMargin + 3px + $d/2;
|
||||
padding-left: $spinnerL + $d/2 + $interiorMargin;
|
||||
.t-title-label {
|
||||
font-style: italic;
|
||||
opacity: 0.6;
|
||||
}
|
||||
&:before {
|
||||
height: $d;
|
||||
width: $d;
|
||||
border-width: 4px;
|
||||
left: $spinnerL;
|
||||
}
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
z-index: 100;
|
||||
}
|
||||
> .holder {
|
||||
//$i: 15%;
|
||||
@include containerSubtle($colorOvrBg, $colorOvrFg);
|
||||
border-radius: $basicCr * 3;
|
||||
color: $colorOvrFg;
|
||||
@@ -57,15 +56,8 @@
|
||||
right: $m;
|
||||
bottom: $m;
|
||||
left: $m;
|
||||
|
||||
//.top-bar,
|
||||
//.editor,
|
||||
//.bottom-bar {
|
||||
// @include absPosDefault();
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ellipsize();
|
||||
font-size: 1.2em;
|
||||
@@ -73,9 +65,7 @@
|
||||
margin-bottom: $interiorMargin;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: pushBack($colorOvrFg, 20%);
|
||||
}
|
||||
.hint, .field-hints { color: $colorFieldHintOverlay !important; }
|
||||
|
||||
.abs.top-bar {
|
||||
height: $ovrTopBarH;
|
||||
@@ -120,7 +110,6 @@
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: visible;
|
||||
//font-size: 1em;
|
||||
height: $ovrFooterH;
|
||||
}
|
||||
|
||||
@@ -132,11 +121,14 @@
|
||||
margin: .5em 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.select {
|
||||
box-shadow: $shdwBtnsOverlay;
|
||||
}
|
||||
}
|
||||
|
||||
.t-dialog-sm .overlay > .holder {
|
||||
// Used for blocker and in-progress dialogs, modal alerts, etc.
|
||||
//@include test(red);
|
||||
$h: 225px;
|
||||
min-height: $h;
|
||||
height: $h;
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
top: $m; right: $m * 1.25; bottom: $m; left: $m * 1.25;
|
||||
}
|
||||
|
||||
body, html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.l-splash-holder {
|
||||
// Main outer holder.
|
||||
@include transition-property(opacity);
|
||||
|
||||
@@ -19,9 +19,6 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<li>
|
||||
<span class="tree-item">
|
||||
<span class="icon wait-spinner"></span>
|
||||
<span class="title-label">Loading...</span>
|
||||
</span>
|
||||
<li class='tree-item t-wait-node loading'>
|
||||
<span class="t-title-label">Loading...</span>
|
||||
</li>
|
||||
|
||||
@@ -32,6 +32,8 @@ define([
|
||||
function TreeNodeView(gestureService, subtreeFactory, selectFn) {
|
||||
this.li = $('<li>');
|
||||
|
||||
this.statusClasses = [];
|
||||
|
||||
this.toggleView = new ToggleView(false);
|
||||
this.toggleView.observe(function (state) {
|
||||
if (state) {
|
||||
@@ -61,6 +63,20 @@ define([
|
||||
this.model(undefined);
|
||||
}
|
||||
|
||||
TreeNodeView.prototype.updateStatusClasses = function (statuses) {
|
||||
this.statusClasses.forEach(function (statusClass) {
|
||||
this.li.removeClass(statusClass);
|
||||
}.bind(this));
|
||||
|
||||
this.statusClasses = statuses.map(function (status) {
|
||||
return 's-status-' + status;
|
||||
});
|
||||
|
||||
this.statusClasses.forEach(function (statusClass) {
|
||||
this.li.addClass(statusClass);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
TreeNodeView.prototype.model = function (domainObject) {
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
@@ -74,6 +90,14 @@ define([
|
||||
$(this.toggleView.elements()).removeClass('has-children');
|
||||
}
|
||||
|
||||
if (domainObject && domainObject.hasCapability('status')) {
|
||||
this.unlisten = domainObject.getCapability('status')
|
||||
.listen(this.updateStatusClasses.bind(this));
|
||||
this.updateStatusClasses(
|
||||
domainObject.getCapability('status').list()
|
||||
);
|
||||
}
|
||||
|
||||
this.labelView.model(domainObject);
|
||||
if (this.subtreeView) {
|
||||
this.subtreeView.model(domainObject);
|
||||
@@ -81,13 +105,13 @@ define([
|
||||
};
|
||||
|
||||
function getIdPath(domainObject) {
|
||||
var context = domainObject && domainObject.getCapability('context');
|
||||
|
||||
function getId(domainObject) {
|
||||
return domainObject.getId();
|
||||
}
|
||||
|
||||
return domainObject ?
|
||||
domainObject.getCapability('context').getPath().map(getId) :
|
||||
[];
|
||||
return context ? context.getPath().map(getId) : [];
|
||||
}
|
||||
|
||||
TreeNodeView.prototype.value = function (domainObject) {
|
||||
|
||||
@@ -60,8 +60,9 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
TreeView.prototype.loadComposition = function (domainObject) {
|
||||
var self = this;
|
||||
TreeView.prototype.loadComposition = function () {
|
||||
var self = this,
|
||||
domainObject = this.activeObject;
|
||||
|
||||
function addNode(domainObject, index) {
|
||||
self.nodeViews[index].model(domainObject);
|
||||
|
||||
@@ -107,12 +107,18 @@ define([
|
||||
mockLocation =
|
||||
jasmine.createSpyObj('location', [ 'isLink' ]),
|
||||
mockMutation =
|
||||
jasmine.createSpyObj('mutation', [ 'listen' ]);
|
||||
jasmine.createSpyObj('mutation', [ 'listen' ]),
|
||||
mockStatus =
|
||||
jasmine.createSpyObj('status', [ 'listen', 'list' ]);
|
||||
|
||||
mockStatus.list.andReturn([]);
|
||||
|
||||
return {
|
||||
context: mockContext,
|
||||
type: mockType,
|
||||
mutation: mockMutation,
|
||||
location: mockLocation
|
||||
location: mockLocation,
|
||||
status: mockStatus
|
||||
};
|
||||
}
|
||||
|
||||
@@ -161,7 +167,7 @@ define([
|
||||
beforeEach(function () {
|
||||
mockComposition.pop();
|
||||
testCapabilities.mutation.listen
|
||||
.mostRecentCall.args[0](mockDomainObject);
|
||||
.mostRecentCall.args[0](mockDomainObject.getModel());
|
||||
waitForCompositionCallback();
|
||||
});
|
||||
|
||||
@@ -200,6 +206,21 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a context-less object is selected", function () {
|
||||
beforeEach(function () {
|
||||
var testCapabilities = makeGenericCapabilities(),
|
||||
mockDomainObject =
|
||||
makeMockDomainObject('xyz', {}, testCapabilities);
|
||||
delete testCapabilities.context;
|
||||
treeView.value(mockDomainObject);
|
||||
});
|
||||
|
||||
it("clears all selection state", function () {
|
||||
var selected = $(treeView.elements()[0]).find('.selected');
|
||||
expect(selected.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when children contain children", function () {
|
||||
beforeEach(function () {
|
||||
var newCapabilities = makeGenericCapabilities(),
|
||||
@@ -247,6 +268,24 @@ define([
|
||||
.toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when status changes", function () {
|
||||
var testStatuses;
|
||||
|
||||
beforeEach(function () {
|
||||
var mockStatus = mockComposition[1].getCapability('status');
|
||||
|
||||
testStatuses = [ 'foo' ];
|
||||
|
||||
mockStatus.list.andReturn(testStatuses);
|
||||
mockStatus.listen.mostRecentCall.args[0](testStatuses);
|
||||
});
|
||||
|
||||
it("reflects the status change in the tree", function () {
|
||||
expect($(treeView.elements()).find('.s-status-foo').length)
|
||||
.toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("observe", function () {
|
||||
|
||||
@@ -27,6 +27,7 @@ $colorBtnIcon: $colorKey;
|
||||
$colorInvokeMenu: #fff;
|
||||
$contrastInvokeMenuPercent: 20%;
|
||||
$shdwBtns: rgba(black, 0.2) 0 1px 2px;
|
||||
$shdwBtnsOverlay: rgba(black, 0.5) 0 1px 5px;
|
||||
$sliderColorBase: $colorKey;
|
||||
$sliderColorRangeHolder: rgba(black, 0.1);
|
||||
$sliderColorRange: rgba($sliderColorBase, 0.3);
|
||||
@@ -76,6 +77,7 @@ $colorInputBg: rgba(#000, 0.1);
|
||||
$colorInputFg: pullForward($colorBodyFg, 20%);
|
||||
$colorFormText: rgba(#fff, 0.5);
|
||||
$colorInputIcon: pushBack($colorBodyFg, 15%);
|
||||
$colorFieldHint: pullForward($colorBodyFg, 20%);
|
||||
|
||||
// Inspector
|
||||
$colorInspectorBg: pullForward($colorBodyBg, 3%);
|
||||
@@ -124,6 +126,7 @@ $colorOvrBg: pullForward($colorBodyBg, 10%);
|
||||
$colorOvrFg: pullForward($colorBodyFg, 30%);
|
||||
$colorOvrBtnBg: pullForward($colorOvrBg, 20%);
|
||||
$colorOvrBtnFg: #fff;
|
||||
$colorFieldHintOverlay: pullForward($colorOvrBg, 30%);
|
||||
|
||||
// Items
|
||||
$colorItemBg: lighten($colorBodyBg, 5%);
|
||||
@@ -173,6 +176,8 @@ $scrollbarTrackShdw: rgba(#000, 0.7) 0 1px 5px;
|
||||
$scrollbarTrackColorBg: rgba(#000, 0.4);
|
||||
$scrollbarThumbColor: lighten($colorBodyBg, 10%);
|
||||
$scrollbarThumbColorHov: lighten($scrollbarThumbColor, 2%);
|
||||
$scrollbarThumbColorOverlay: lighten($colorOvrBg, 10%);
|
||||
$scrollbarThumbColorOverlayHov: lighten($scrollbarThumbColorOverlay, 2%);
|
||||
|
||||
// Splitter
|
||||
$splitterD: 25px; // splitterD and HandleD should both be odd, or even
|
||||
|
||||
@@ -27,6 +27,7 @@ $colorBtnIcon: #eee;
|
||||
$colorInvokeMenu: #000;
|
||||
$contrastInvokeMenuPercent: 40%;
|
||||
$shdwBtns: none;
|
||||
$shdwBtnsOverlay: none;
|
||||
$sliderColorBase: $colorKey;
|
||||
$sliderColorRangeHolder: rgba(black, 0.07);
|
||||
$sliderColorRange: rgba($sliderColorBase, 0.2);
|
||||
@@ -76,6 +77,7 @@ $colorInputBg: $colorGenBg;
|
||||
$colorInputFg: $colorBodyFg;
|
||||
$colorFormText: pushBack($colorBodyFg, 10%);
|
||||
$colorInputIcon: pushBack($colorBodyFg, 25%);
|
||||
$colorFieldHint: pullForward($colorBodyFg, 40%);
|
||||
|
||||
// Inspector
|
||||
$colorInspectorBg: pullForward($colorBodyBg, 5%);
|
||||
@@ -124,6 +126,7 @@ $colorOvrBg: $colorBodyBg;
|
||||
$colorOvrFg: $colorBodyFg;
|
||||
$colorOvrBtnBg: pullForward($colorOvrBg, 40%);
|
||||
$colorOvrBtnFg: #fff;
|
||||
$colorFieldHintOverlay: pullForward($colorOvrBg, 40%);
|
||||
|
||||
// Items
|
||||
$colorItemBg: #ddd;
|
||||
@@ -170,9 +173,11 @@ $shdwItemTreeIcon: none;
|
||||
// Scrollbar
|
||||
$scrollbarTrackSize: 10px;
|
||||
$scrollbarTrackShdw: rgba(#000, 0.2) 0 1px 2px;
|
||||
$scrollbarTrackColorBg: rgba(#000, 0.1);
|
||||
$scrollbarThumbColor: darken($colorBodyBg, 50%);//
|
||||
$scrollbarTrackColorBg: rgba(#000, 0.2);
|
||||
$scrollbarThumbColor: darken($colorBodyBg, 50%);
|
||||
$scrollbarThumbColorHov: $colorKey;
|
||||
$scrollbarThumbColorOverlay: darken($colorOvrBg, 50%);
|
||||
$scrollbarThumbColorOverlayHov: $scrollbarThumbColorHov;
|
||||
|
||||
// Splitter
|
||||
$splitterD: 24px;
|
||||
|
||||
@@ -27,6 +27,7 @@ define([
|
||||
"./src/models/StaticModelProvider",
|
||||
"./src/models/RootModelProvider",
|
||||
"./src/models/ModelAggregator",
|
||||
"./src/models/ModelCacheService",
|
||||
"./src/models/PersistedModelProvider",
|
||||
"./src/models/CachingModelDecorator",
|
||||
"./src/models/MissingModelDecorator",
|
||||
@@ -58,6 +59,7 @@ define([
|
||||
StaticModelProvider,
|
||||
RootModelProvider,
|
||||
ModelAggregator,
|
||||
ModelCacheService,
|
||||
PersistedModelProvider,
|
||||
CachingModelDecorator,
|
||||
MissingModelDecorator,
|
||||
@@ -182,7 +184,10 @@ define([
|
||||
{
|
||||
"provides": "modelService",
|
||||
"type": "decorator",
|
||||
"implementation": CachingModelDecorator
|
||||
"implementation": CachingModelDecorator,
|
||||
"depends": [
|
||||
"cacheService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"provides": "modelService",
|
||||
@@ -248,21 +253,22 @@ define([
|
||||
"property": "name",
|
||||
"pattern": "\\S+",
|
||||
"required": true,
|
||||
"cssclass": "l-med"
|
||||
"cssclass": "l-input-lg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "root",
|
||||
"name": "Root",
|
||||
"glyph": "F"
|
||||
"glyph": "\u0046"
|
||||
},
|
||||
{
|
||||
"key": "folder",
|
||||
"name": "Folder",
|
||||
"glyph": "F",
|
||||
"glyph": "\u0046",
|
||||
"features": "creation",
|
||||
"description": "Useful for storing and organizing domain objects.",
|
||||
"description": "Create folders to organize other objects or links to objects.",
|
||||
"priority": 1000,
|
||||
"model": {
|
||||
"composition": []
|
||||
}
|
||||
@@ -270,11 +276,11 @@ define([
|
||||
{
|
||||
"key": "unknown",
|
||||
"name": "Unknown Type",
|
||||
"glyph": "?"
|
||||
"glyph": "\u003f"
|
||||
},
|
||||
{
|
||||
"name": "Unknown Type",
|
||||
"glyph": "?"
|
||||
"glyph": "\u003f"
|
||||
}
|
||||
],
|
||||
"capabilities": [
|
||||
@@ -319,6 +325,7 @@ define([
|
||||
"key": "persistence",
|
||||
"implementation": PersistenceCapability,
|
||||
"depends": [
|
||||
"cacheService",
|
||||
"persistenceService",
|
||||
"identifierService",
|
||||
"notificationService",
|
||||
@@ -349,11 +356,16 @@ define([
|
||||
"implementation": InstantiationCapability,
|
||||
"depends": [
|
||||
"$injector",
|
||||
"identifierService"
|
||||
"identifierService",
|
||||
"now"
|
||||
]
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"key": "cacheService",
|
||||
"implementation": ModelCacheService
|
||||
},
|
||||
{
|
||||
"key": "now",
|
||||
"implementation": Now
|
||||
@@ -384,7 +396,8 @@ define([
|
||||
"implementation": Instantiate,
|
||||
"depends": [
|
||||
"capabilityService",
|
||||
"identifierService"
|
||||
"identifierService",
|
||||
"cacheService"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -28,7 +28,8 @@ define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
var DISALLOWED_ACTIONS = ["move", "copy", "link", "window", "follow"];
|
||||
var DISALLOWED_ACTIONS = ["copy", "window", "follow"];
|
||||
|
||||
/**
|
||||
* The ActionCapability allows applicable Actions to be retrieved and
|
||||
* performed for specific domain objects, e.g.:
|
||||
|
||||
@@ -35,10 +35,16 @@ define(
|
||||
* @param $injector Angular's `$injector`
|
||||
* @implements {Capability}
|
||||
*/
|
||||
function InstantiationCapability($injector, identifierService, domainObject) {
|
||||
function InstantiationCapability(
|
||||
$injector,
|
||||
identifierService,
|
||||
now,
|
||||
domainObject
|
||||
) {
|
||||
this.$injector = $injector;
|
||||
this.identifierService = identifierService;
|
||||
this.domainObject = domainObject;
|
||||
this.now = now;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +63,8 @@ define(
|
||||
space = parsedId.getDefinedSpace(),
|
||||
id = this.identifierService.generate(space);
|
||||
|
||||
model.modified = this.now();
|
||||
|
||||
// Lazily initialize; instantiate depends on capabilityService,
|
||||
// which depends on all capabilities, including this one.
|
||||
this.instantiateFn = this.instantiateFn ||
|
||||
|
||||
@@ -46,6 +46,7 @@ define(
|
||||
* @implements {Capability}
|
||||
*/
|
||||
function PersistenceCapability(
|
||||
cacheService,
|
||||
persistenceService,
|
||||
identifierService,
|
||||
notificationService,
|
||||
@@ -56,6 +57,7 @@ define(
|
||||
this.modified = domainObject.getModel().modified;
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.cacheService = cacheService;
|
||||
this.identifierService = identifierService;
|
||||
this.persistenceService = persistenceService;
|
||||
this.notificationService = notificationService;
|
||||
@@ -130,6 +132,7 @@ define(
|
||||
domainObject = this.domainObject,
|
||||
model = domainObject.getModel(),
|
||||
modified = model.modified,
|
||||
cacheService = this.cacheService,
|
||||
persistenceService = this.persistenceService,
|
||||
persistenceFn = model.persisted !== undefined ?
|
||||
this.persistenceService.updateObject :
|
||||
|
||||
@@ -35,9 +35,8 @@ define(
|
||||
* @param {ModelService} modelService this service to decorate
|
||||
* @implements {ModelService}
|
||||
*/
|
||||
function CachingModelDecorator(modelService) {
|
||||
this.cache = {};
|
||||
this.cached = {};
|
||||
function CachingModelDecorator(cacheService, modelService) {
|
||||
this.cacheService = cacheService;
|
||||
this.modelService = modelService;
|
||||
}
|
||||
|
||||
@@ -51,17 +50,16 @@ define(
|
||||
}
|
||||
|
||||
CachingModelDecorator.prototype.getModels = function (ids) {
|
||||
var cache = this.cache,
|
||||
cached = this.cached,
|
||||
var cacheService = this.cacheService,
|
||||
neededIds = ids.filter(function notCached(id) {
|
||||
return !cached[id];
|
||||
return !cacheService.has(id);
|
||||
});
|
||||
|
||||
// Update the cached instance of a model to a new value.
|
||||
// We update in-place to ensure there is only ever one instance
|
||||
// of any given model exposed by the modelService as a whole.
|
||||
function updateModel(id, model) {
|
||||
var oldModel = cache[id];
|
||||
var oldModel = cacheService.get(id);
|
||||
|
||||
// Same object instance is a possibility, so don't copy
|
||||
if (oldModel === model) {
|
||||
@@ -71,7 +69,7 @@ define(
|
||||
// If we'd previously cached an undefined value, or are now
|
||||
// seeing undefined, replace the item in the cache entirely.
|
||||
if (oldModel === undefined || model === undefined) {
|
||||
cache[id] = model;
|
||||
cacheService.put(id, model);
|
||||
return model;
|
||||
}
|
||||
|
||||
@@ -91,15 +89,15 @@ define(
|
||||
// Store the provided models in our cache
|
||||
function cacheAll(models) {
|
||||
Object.keys(models).forEach(function (id) {
|
||||
cache[id] = cached[id] ?
|
||||
var model = cacheService.has(id) ?
|
||||
updateModel(id, models[id]) : models[id];
|
||||
cached[id] = true;
|
||||
cacheService.put(id, model);
|
||||
});
|
||||
}
|
||||
|
||||
// Expose the cache (for promise chaining)
|
||||
function giveCache() {
|
||||
return cache;
|
||||
return cacheService.all();
|
||||
}
|
||||
|
||||
// Look up if we have unknown IDs
|
||||
@@ -110,7 +108,7 @@ define(
|
||||
}
|
||||
|
||||
// Otherwise, just expose the cache directly
|
||||
return fastPromise(cache);
|
||||
return fastPromise(cacheService.all());
|
||||
};
|
||||
|
||||
return CachingModelDecorator;
|
||||
|
||||
83
platform/core/src/models/ModelCacheService.js
Normal file
83
platform/core/src/models/ModelCacheService.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Provides a cache for domain object models which exist in memory,
|
||||
* but may or may not exist in backing persistene stores.
|
||||
* @constructor
|
||||
* @memberof platform/core
|
||||
*/
|
||||
function ModelCacheService() {
|
||||
this.cache = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a domain object model in the cache.
|
||||
* @param {string} id the domain object's identifier
|
||||
* @param {object} model the domain object's model
|
||||
*/
|
||||
ModelCacheService.prototype.put = function (id, model) {
|
||||
this.cache[id] = model;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve a domain object model from the cache.
|
||||
* @param {string} id the domain object's identifier
|
||||
* @returns {object} the domain object's model
|
||||
*/
|
||||
ModelCacheService.prototype.get = function (id) {
|
||||
return this.cache[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a domain object model is in the cache.
|
||||
* @param {string} id the domain object's identifier
|
||||
* @returns {boolean} true if present; false if not
|
||||
*/
|
||||
ModelCacheService.prototype.has = function (id) {
|
||||
return this.cache.hasOwnProperty(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a domain object model from the cache.
|
||||
* @param {string} id the domain object's identifier
|
||||
*/
|
||||
ModelCacheService.prototype.remove = function (id) {
|
||||
delete this.cache[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve all cached domain object models. These are given
|
||||
* as an object containing key-value pairs, where keys are
|
||||
* domain object identifiers and values are domain object models.
|
||||
* @returns {object} all domain object models
|
||||
*/
|
||||
ModelCacheService.prototype.all = function () {
|
||||
return this.cache;
|
||||
};
|
||||
|
||||
return ModelCacheService;
|
||||
});
|
||||
@@ -44,10 +44,15 @@ define(
|
||||
* @param {IdentifierService} identifierService service to generate
|
||||
* new identifiers
|
||||
*/
|
||||
function Instantiate(capabilityService, identifierService) {
|
||||
function Instantiate(
|
||||
capabilityService,
|
||||
identifierService,
|
||||
cacheService
|
||||
) {
|
||||
return function (model, id) {
|
||||
var capabilities = capabilityService.getCapabilities(model);
|
||||
id = id || identifierService.generate();
|
||||
cacheService.put(id, model);
|
||||
return new DomainObjectImpl(id, model, capabilities);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ define(
|
||||
mockIdentifierService,
|
||||
mockInstantiate,
|
||||
mockIdentifier,
|
||||
mockNow,
|
||||
mockDomainObject,
|
||||
instantiation;
|
||||
|
||||
@@ -57,9 +58,13 @@ define(
|
||||
mockIdentifierService.parse.andReturn(mockIdentifier);
|
||||
mockIdentifierService.generate.andReturn("some-id");
|
||||
|
||||
mockNow = jasmine.createSpy();
|
||||
mockNow.andReturn(1234321);
|
||||
|
||||
instantiation = new InstantiationCapability(
|
||||
mockInjector,
|
||||
mockIdentifierService,
|
||||
mockNow,
|
||||
mockDomainObject
|
||||
);
|
||||
});
|
||||
@@ -81,7 +86,10 @@ define(
|
||||
expect(instantiation.instantiate(testModel))
|
||||
.toBe(mockDomainObject);
|
||||
expect(mockInstantiate)
|
||||
.toHaveBeenCalledWith(testModel, jasmine.any(String));
|
||||
.toHaveBeenCalledWith({
|
||||
someKey: "some value",
|
||||
modified: mockNow()
|
||||
}, jasmine.any(String));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -36,6 +36,7 @@ define(
|
||||
mockDomainObject,
|
||||
mockIdentifier,
|
||||
mockNofificationService,
|
||||
mockCacheService,
|
||||
mockQ,
|
||||
id = "object id",
|
||||
model,
|
||||
@@ -81,6 +82,10 @@ define(
|
||||
"notificationService",
|
||||
["error"]
|
||||
);
|
||||
mockCacheService = jasmine.createSpyObj(
|
||||
"cacheService",
|
||||
[ "get", "put", "remove", "all" ]
|
||||
);
|
||||
|
||||
mockDomainObject = {
|
||||
getId: function () { return id; },
|
||||
@@ -96,6 +101,7 @@ define(
|
||||
mockIdentifierService.parse.andReturn(mockIdentifier);
|
||||
mockIdentifier.getSpace.andReturn(SPACE);
|
||||
persistence = new PersistenceCapability(
|
||||
mockCacheService,
|
||||
mockPersistenceService,
|
||||
mockIdentifierService,
|
||||
mockNofificationService,
|
||||
@@ -171,6 +177,7 @@ define(
|
||||
expect(mockNofificationService.error).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("unsuccessful persistence", function() {
|
||||
var sadPromise = {
|
||||
then: function(callback){
|
||||
|
||||
@@ -22,8 +22,11 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/models/CachingModelDecorator"],
|
||||
function (CachingModelDecorator) {
|
||||
[
|
||||
"../../src/models/CachingModelDecorator",
|
||||
"../../src/models/ModelCacheService"
|
||||
],
|
||||
function (CachingModelDecorator, ModelCacheService) {
|
||||
"use strict";
|
||||
|
||||
describe("The caching model decorator", function () {
|
||||
@@ -67,7 +70,10 @@ define(
|
||||
b: { someOtherKey: "some other value" }
|
||||
};
|
||||
mockModelService.getModels.andReturn(asPromise(testModels));
|
||||
decorator = new CachingModelDecorator(mockModelService);
|
||||
decorator = new CachingModelDecorator(
|
||||
new ModelCacheService(),
|
||||
mockModelService
|
||||
);
|
||||
});
|
||||
|
||||
it("loads models from its wrapped model service", function () {
|
||||
@@ -150,4 +156,4 @@ define(
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
69
platform/core/test/models/ModelCacheServiceSpec.js
Normal file
69
platform/core/test/models/ModelCacheServiceSpec.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(['../../src/models/ModelCacheService'], function (ModelCacheService) {
|
||||
'use strict';
|
||||
describe("ModelCacheService", function () {
|
||||
var testIds,
|
||||
testModels,
|
||||
cacheService;
|
||||
|
||||
beforeEach(function () {
|
||||
testIds = [ 'a', 'b', 'c', 'd' ];
|
||||
testModels = testIds.reduce(function (models, id) {
|
||||
models[id] = { someKey: "some value for " + id };
|
||||
return models;
|
||||
}, {});
|
||||
cacheService = new ModelCacheService();
|
||||
});
|
||||
|
||||
describe("when populated with models", function () {
|
||||
beforeEach(function () {
|
||||
testIds.forEach(function (id) {
|
||||
cacheService.put(id, testModels[id]);
|
||||
});
|
||||
});
|
||||
|
||||
it("indicates that it has these models", function () {
|
||||
testIds.forEach(function (id) {
|
||||
expect(cacheService.has(id)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("provides all of these models", function () {
|
||||
expect(cacheService.all()).toEqual(testModels);
|
||||
});
|
||||
|
||||
it("allows models to be retrieved", function () {
|
||||
testIds.forEach(function (id) {
|
||||
expect(cacheService.get(id)).toEqual(testModels[id]);
|
||||
});
|
||||
});
|
||||
|
||||
it("allows models to be removed", function () {
|
||||
cacheService.remove('a');
|
||||
expect(cacheService.has('a')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -32,8 +32,7 @@ define(
|
||||
mockIdentifierService,
|
||||
mockCapabilityConstructor,
|
||||
mockCapabilityInstance,
|
||||
mockCapabilities,
|
||||
mockIdentifier,
|
||||
mockCacheService,
|
||||
idCounter,
|
||||
testModel,
|
||||
instantiate,
|
||||
@@ -62,11 +61,17 @@ define(
|
||||
"some-id-" + (idCounter += 1);
|
||||
});
|
||||
|
||||
mockCacheService = jasmine.createSpyObj(
|
||||
'cacheService',
|
||||
[ 'get', 'put', 'remove', 'all' ]
|
||||
);
|
||||
|
||||
testModel = { someKey: "some value" };
|
||||
|
||||
instantiate = new Instantiate(
|
||||
mockCapabilityService,
|
||||
mockIdentifierService
|
||||
mockIdentifierService,
|
||||
mockCacheService
|
||||
);
|
||||
domainObject = instantiate(testModel);
|
||||
});
|
||||
@@ -92,6 +97,13 @@ define(
|
||||
expect(instantiate(testModel).getId())
|
||||
.not.toEqual(domainObject.getId());
|
||||
});
|
||||
|
||||
it("caches the instantiated model", function () {
|
||||
expect(mockCacheService.put).toHaveBeenCalledWith(
|
||||
domainObject.getId(),
|
||||
testModel
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ define([
|
||||
"./src/actions/SetPrimaryLocationAction",
|
||||
"./src/services/LocatingCreationDecorator",
|
||||
"./src/services/LocatingObjectDecorator",
|
||||
"./src/policies/CopyPolicy",
|
||||
"./src/policies/CrossSpacePolicy",
|
||||
"./src/policies/MovePolicy",
|
||||
"./src/capabilities/LocationCapability",
|
||||
"./src/services/MoveService",
|
||||
"./src/services/LinkService",
|
||||
@@ -44,7 +46,9 @@ define([
|
||||
SetPrimaryLocationAction,
|
||||
LocatingCreationDecorator,
|
||||
LocatingObjectDecorator,
|
||||
CopyPolicy,
|
||||
CrossSpacePolicy,
|
||||
MovePolicy,
|
||||
LocationCapability,
|
||||
MoveService,
|
||||
LinkService,
|
||||
@@ -140,6 +144,14 @@ define([
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": CrossSpacePolicy
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": CopyPolicy
|
||||
},
|
||||
{
|
||||
"category": "action",
|
||||
"implementation": MovePolicy
|
||||
}
|
||||
],
|
||||
"capabilities": [
|
||||
|
||||
@@ -20,44 +20,38 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
/*global define */
|
||||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
|
||||
function EnumeratedTelemetrySeries(data) {
|
||||
this.data = data;
|
||||
/**
|
||||
* Disallow duplication when the object to be duplicated is not
|
||||
* creatable.
|
||||
* @constructor
|
||||
* @implements {Policy}
|
||||
* @memberof platform/entanglement
|
||||
*/
|
||||
function CopyPolicy() {
|
||||
}
|
||||
|
||||
EnumeratedTelemetrySeries.prototype.getPointCount = function (index) {
|
||||
return this.data.length;
|
||||
};
|
||||
function allowCreation(domainObject) {
|
||||
var type = domainObject && domainObject.getCapability('type');
|
||||
return !!(type && type.hasFeature('creation'));
|
||||
}
|
||||
|
||||
EnumeratedTelemetrySeries.prototype.getDatum = function (index) {
|
||||
if (index > this.data.length || index < 0) {
|
||||
throw new Error('IndexOutOfRange: index not available in series.');
|
||||
function selectedObject(context) {
|
||||
return context.selectedObject || context.domainObject;
|
||||
}
|
||||
|
||||
CopyPolicy.prototype.allow = function (action, context) {
|
||||
var key = action.getMetadata().key;
|
||||
|
||||
if (key === 'copy') {
|
||||
return allowCreation(selectedObject(context));
|
||||
}
|
||||
return this.data[index];
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
EnumeratedTelemetrySeries.prototype.getRangeValue = function (
|
||||
index,
|
||||
range
|
||||
) {
|
||||
range = range || 'eu';
|
||||
return this.getDatum(index)[range];
|
||||
};
|
||||
|
||||
EnumeratedTelemetrySeries.prototype.getDomainValue = function (
|
||||
index,
|
||||
domain
|
||||
) {
|
||||
domain = domain || 'time';
|
||||
return this.getDatum(index)[domain];
|
||||
};
|
||||
|
||||
return EnumeratedTelemetrySeries;
|
||||
return CopyPolicy;
|
||||
});
|
||||
63
platform/entanglement/src/policies/MovePolicy.js
Normal file
63
platform/entanglement/src/policies/MovePolicy.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*global define */
|
||||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Disallow moves when either the parent or the child are not
|
||||
* modifiable by users.
|
||||
* @constructor
|
||||
* @implements {Policy}
|
||||
* @memberof platform/entanglement
|
||||
*/
|
||||
function MovePolicy() {
|
||||
}
|
||||
|
||||
function parentOf(domainObject) {
|
||||
var context = domainObject.getCapability('context');
|
||||
return context && context.getParent();
|
||||
}
|
||||
|
||||
function allowMutation(domainObject) {
|
||||
var type = domainObject && domainObject.getCapability('type');
|
||||
return !!(type && type.hasFeature('creation'));
|
||||
}
|
||||
|
||||
function selectedObject(context) {
|
||||
return context.selectedObject || context.domainObject;
|
||||
}
|
||||
|
||||
MovePolicy.prototype.allow = function (action, context) {
|
||||
var key = action.getMetadata().key;
|
||||
|
||||
if (key === 'move') {
|
||||
return allowMutation(selectedObject(context)) &&
|
||||
allowMutation(parentOf(selectedObject(context)));
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return MovePolicy;
|
||||
});
|
||||
94
platform/entanglement/test/policies/CopyPolicySpec.js
Normal file
94
platform/entanglement/test/policies/CopyPolicySpec.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*global define,describe,beforeEach,it,jasmine,expect,spyOn */
|
||||
define([
|
||||
'../../src/policies/CopyPolicy',
|
||||
'../DomainObjectFactory'
|
||||
], function (CopyPolicy, domainObjectFactory) {
|
||||
'use strict';
|
||||
|
||||
describe("CopyPolicy", function () {
|
||||
var testMetadata,
|
||||
testContext,
|
||||
mockDomainObject,
|
||||
mockType,
|
||||
mockAction,
|
||||
policy;
|
||||
|
||||
beforeEach(function () {
|
||||
mockType =
|
||||
jasmine.createSpyObj('type', ['hasFeature']);
|
||||
|
||||
testMetadata = {};
|
||||
|
||||
mockDomainObject = domainObjectFactory({
|
||||
capabilities: { type: mockType }
|
||||
});
|
||||
|
||||
mockType.hasFeature.andCallFake(function (feature) {
|
||||
return feature === 'creation';
|
||||
});
|
||||
|
||||
mockAction = jasmine.createSpyObj('action', ['getMetadata']);
|
||||
mockAction.getMetadata.andReturn(testMetadata);
|
||||
|
||||
testContext = { domainObject: mockDomainObject };
|
||||
|
||||
policy = new CopyPolicy();
|
||||
});
|
||||
|
||||
describe("for copy actions", function () {
|
||||
beforeEach(function () {
|
||||
testMetadata.key = 'copy';
|
||||
});
|
||||
|
||||
describe("when an object is non-creatable", function () {
|
||||
beforeEach(function () {
|
||||
mockType.hasFeature.andReturn(false);
|
||||
});
|
||||
|
||||
it("disallows the action", function () {
|
||||
expect(policy.allow(mockAction, testContext)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when an object is creatable", function () {
|
||||
it("allows the action", function () {
|
||||
expect(policy.allow(mockAction, testContext)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("for other actions", function () {
|
||||
beforeEach(function () {
|
||||
testMetadata.key = 'foo';
|
||||
});
|
||||
|
||||
it("simply allows the action", function () {
|
||||
expect(policy.allow(mockAction, testContext)).toBe(true);
|
||||
mockType.hasFeature.andReturn(false);
|
||||
expect(policy.allow(mockAction, testContext)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
127
platform/entanglement/test/policies/MovePolicySpec.js
Normal file
127
platform/entanglement/test/policies/MovePolicySpec.js
Normal file
@@ -0,0 +1,127 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*global define,describe,beforeEach,it,jasmine,expect,spyOn */
|
||||
define([
|
||||
'../../src/policies/MovePolicy',
|
||||
'../DomainObjectFactory'
|
||||
], function (MovePolicy, domainObjectFactory) {
|
||||
'use strict';
|
||||
|
||||
describe("MovePolicy", function () {
|
||||
var testMetadata,
|
||||
testContext,
|
||||
mockDomainObject,
|
||||
mockParent,
|
||||
mockParentType,
|
||||
mockType,
|
||||
mockAction,
|
||||
policy;
|
||||
|
||||
beforeEach(function () {
|
||||
var mockContextCapability =
|
||||
jasmine.createSpyObj('context', ['getParent']);
|
||||
|
||||
mockType =
|
||||
jasmine.createSpyObj('type', ['hasFeature']);
|
||||
mockParentType =
|
||||
jasmine.createSpyObj('parent-type', ['hasFeature']);
|
||||
|
||||
|
||||
testMetadata = {};
|
||||
|
||||
mockDomainObject = domainObjectFactory({
|
||||
capabilities: {
|
||||
context: mockContextCapability,
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
mockParent = domainObjectFactory({
|
||||
capabilities: {
|
||||
type: mockParentType
|
||||
}
|
||||
});
|
||||
|
||||
mockContextCapability.getParent.andReturn(mockParent);
|
||||
|
||||
mockType.hasFeature.andCallFake(function (feature) {
|
||||
return feature === 'creation';
|
||||
});
|
||||
mockParentType.hasFeature.andCallFake(function (feature) {
|
||||
return feature === 'creation';
|
||||
});
|
||||
|
||||
mockAction = jasmine.createSpyObj('action', ['getMetadata']);
|
||||
mockAction.getMetadata.andReturn(testMetadata);
|
||||
|
||||
testContext = { domainObject: mockDomainObject };
|
||||
|
||||
policy = new MovePolicy();
|
||||
});
|
||||
|
||||
describe("for move actions", function () {
|
||||
beforeEach(function () {
|
||||
testMetadata.key = 'move';
|
||||
});
|
||||
|
||||
describe("when an object is non-modifiable", function () {
|
||||
beforeEach(function () {
|
||||
mockType.hasFeature.andReturn(false);
|
||||
});
|
||||
|
||||
it("disallows the action", function () {
|
||||
expect(policy.allow(mockAction, testContext)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a parent is non-modifiable", function () {
|
||||
beforeEach(function () {
|
||||
mockParentType.hasFeature.andReturn(false);
|
||||
});
|
||||
|
||||
it("disallows the action", function () {
|
||||
expect(policy.allow(mockAction, testContext)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when an object and its parent are modifiable", function () {
|
||||
it("allows the action", function () {
|
||||
expect(policy.allow(mockAction, testContext)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("for other actions", function () {
|
||||
beforeEach(function () {
|
||||
testMetadata.key = 'foo';
|
||||
});
|
||||
|
||||
it("simply allows the action", function () {
|
||||
expect(policy.allow(mockAction, testContext)).toBe(true);
|
||||
mockType.hasFeature.andReturn(false);
|
||||
expect(policy.allow(mockAction, testContext)).toBe(true);
|
||||
mockParentType.hasFeature.andReturn(false);
|
||||
expect(policy.allow(mockAction, testContext)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -157,7 +157,9 @@ define([
|
||||
{
|
||||
"key": "clock",
|
||||
"name": "Clock",
|
||||
"glyph": "C",
|
||||
"glyph": "\u0043",
|
||||
"description": "A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.",
|
||||
"priority": 101,
|
||||
"features": [
|
||||
"creation"
|
||||
],
|
||||
@@ -182,7 +184,8 @@ define([
|
||||
"value": "hh:mm:ss",
|
||||
"name": "hh:mm:ss"
|
||||
}
|
||||
]
|
||||
],
|
||||
"cssclass": "l-inline"
|
||||
},
|
||||
{
|
||||
"control": "select",
|
||||
@@ -195,7 +198,8 @@ define([
|
||||
"value": "clock24",
|
||||
"name": "24hr"
|
||||
}
|
||||
]
|
||||
],
|
||||
"cssclass": "l-inline"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -210,7 +214,9 @@ define([
|
||||
{
|
||||
"key": "timer",
|
||||
"name": "Timer",
|
||||
"glyph": "õ",
|
||||
"glyph": "\u00f5",
|
||||
"description": "A timer that counts up or down to a datetime. Timers can be started, stopped and reset whenever needed, and support a variety of display formats. Each Timer displays the same value to all users. Timers can be added to Display Layouts.",
|
||||
"priority": 100,
|
||||
"features": [
|
||||
"creation"
|
||||
],
|
||||
@@ -223,6 +229,7 @@ define([
|
||||
{
|
||||
"key": "timerFormat",
|
||||
"control": "select",
|
||||
"name": "Display Format",
|
||||
"options": [
|
||||
{
|
||||
"value": "long",
|
||||
|
||||
@@ -58,7 +58,7 @@ define([
|
||||
{
|
||||
"key": "layout",
|
||||
"name": "Display Layout",
|
||||
"glyph": "L",
|
||||
"glyph": "\u004c",
|
||||
"type": "layout",
|
||||
"template": layoutTemplate,
|
||||
"editable": true,
|
||||
@@ -82,28 +82,28 @@ define([
|
||||
"items": [
|
||||
{
|
||||
"method": "add",
|
||||
"glyph": "+",
|
||||
"glyph": "\u002b",
|
||||
"control": "menu-button",
|
||||
"text": "Add",
|
||||
"options": [
|
||||
{
|
||||
"name": "Box",
|
||||
"glyph": "à",
|
||||
"glyph": "\u00e0",
|
||||
"key": "fixed.box"
|
||||
},
|
||||
{
|
||||
"name": "Line",
|
||||
"glyph": "â",
|
||||
"glyph": "\u00e2",
|
||||
"key": "fixed.line"
|
||||
},
|
||||
{
|
||||
"name": "Text",
|
||||
"glyph": "ä",
|
||||
"glyph": "\u00e4",
|
||||
"key": "fixed.text"
|
||||
},
|
||||
{
|
||||
"name": "Image",
|
||||
"glyph": "ã",
|
||||
"glyph": "\u00e3",
|
||||
"key": "fixed.image"
|
||||
}
|
||||
]
|
||||
@@ -119,22 +119,22 @@ define([
|
||||
"options": [
|
||||
{
|
||||
"name": "Move to Top",
|
||||
"glyph": "^",
|
||||
"glyph": "\u00eb",
|
||||
"key": "top"
|
||||
},
|
||||
{
|
||||
"name": "Move Up",
|
||||
"glyph": "^",
|
||||
"glyph": "\u005e",
|
||||
"key": "up"
|
||||
},
|
||||
{
|
||||
"name": "Move Down",
|
||||
"glyph": "v",
|
||||
"glyph": "\u0076",
|
||||
"key": "down"
|
||||
},
|
||||
{
|
||||
"name": "Move to Bottom",
|
||||
"glyph": "v",
|
||||
"glyph": "\u00ee",
|
||||
"key": "bottom"
|
||||
}
|
||||
]
|
||||
@@ -263,8 +263,9 @@ define([
|
||||
{
|
||||
"key": "layout",
|
||||
"name": "Display Layout",
|
||||
"glyph": "L",
|
||||
"description": "A layout in which multiple telemetry panels may be displayed.",
|
||||
"glyph": "\u004c",
|
||||
"description": "Assemble other objects and components together into a reusable screen layout. Working in a simple canvas workspace, simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.",
|
||||
"priority": 900,
|
||||
"features": "creation",
|
||||
"model": {
|
||||
"composition": []
|
||||
@@ -278,12 +279,12 @@ define([
|
||||
{
|
||||
"name": "Horizontal grid (px)",
|
||||
"control": "textfield",
|
||||
"cssclass": "l-small l-numeric"
|
||||
"cssclass": "l-input-sm l-numeric"
|
||||
},
|
||||
{
|
||||
"name": "Vertical grid (px)",
|
||||
"control": "textfield",
|
||||
"cssclass": "l-small l-numeric"
|
||||
"cssclass": "l-input-sm l-numeric"
|
||||
}
|
||||
],
|
||||
"key": "layoutGrid",
|
||||
@@ -296,6 +297,7 @@ define([
|
||||
"name": "Telemetry Panel",
|
||||
"glyph": "t",
|
||||
"description": "A panel for collecting telemetry elements.",
|
||||
"priority": 899,
|
||||
"delegates": [
|
||||
"telemetry"
|
||||
],
|
||||
|
||||
@@ -38,8 +38,9 @@ define([
|
||||
{
|
||||
"key": "example.page",
|
||||
"name": "Web Page",
|
||||
"glyph": "ê",
|
||||
"description": "A component to display a web page or image with a valid URL. Can be added to a Display Layout.",
|
||||
"glyph": "\u00ea",
|
||||
"description": "Embed a web page or web-based image in a resizeable window component. Can be added to Display Layouts. Note that the URL being embedded must allow iframing.",
|
||||
"priority": 50,
|
||||
"features": [
|
||||
"creation"
|
||||
],
|
||||
@@ -49,7 +50,8 @@ define([
|
||||
"name": "URL",
|
||||
"control": "textfield",
|
||||
"pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$",
|
||||
"required": true
|
||||
"required": true,
|
||||
"cssclass": "l-input-lg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ define(
|
||||
function () {
|
||||
'use strict';
|
||||
|
||||
var DIGITS = 3;
|
||||
|
||||
/**
|
||||
* Wraps a `TelemetryFormatter` to provide formats for domain and
|
||||
* range values; provides a single place to track domain/range
|
||||
@@ -63,6 +65,10 @@ define(
|
||||
};
|
||||
|
||||
PlotTelemetryFormatter.prototype.formatRangeValue = function (value) {
|
||||
if (typeof value === 'number') {
|
||||
return value.toFixed(DIGITS);
|
||||
}
|
||||
|
||||
return this.telemetryFormatter
|
||||
.formatRangeValue(value, this.rangeFormat);
|
||||
};
|
||||
|
||||
@@ -55,14 +55,17 @@ define(
|
||||
.toHaveBeenCalledWith(12321, domainFormat);
|
||||
});
|
||||
|
||||
it("includes format in formatRangeValue calls", function () {
|
||||
it("includes format in formatRangeValue calls for strings", function () {
|
||||
mockFormatter.formatRangeValue.andReturn("formatted!");
|
||||
expect(formatter.formatRangeValue(12321))
|
||||
expect(formatter.formatRangeValue('foo'))
|
||||
.toEqual("formatted!");
|
||||
expect(mockFormatter.formatRangeValue)
|
||||
.toHaveBeenCalledWith(12321, rangeFormat);
|
||||
.toHaveBeenCalledWith('foo', rangeFormat);
|
||||
});
|
||||
|
||||
it("formats numeric values with three fixed digits", function () {
|
||||
expect(formatter.formatRangeValue(10)).toEqual("10.000");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -23,16 +23,16 @@
|
||||
|
||||
define([
|
||||
"./src/directives/MCTTable",
|
||||
"./src/controllers/RTTelemetryTableController",
|
||||
"./src/controllers/TelemetryTableController",
|
||||
"./src/controllers/RealtimeTableController",
|
||||
"./src/controllers/HistoricalTableController",
|
||||
"./src/controllers/TableOptionsController",
|
||||
'../../commonUI/regions/src/Region',
|
||||
'../../commonUI/browse/src/InspectorRegion',
|
||||
"legacyRegistry"
|
||||
], function (
|
||||
MCTTable,
|
||||
RTTelemetryTableController,
|
||||
TelemetryTableController,
|
||||
RealtimeTableController,
|
||||
HistoricalTableController,
|
||||
TableOptionsController,
|
||||
Region,
|
||||
InspectorRegion,
|
||||
@@ -62,8 +62,9 @@ define([
|
||||
{
|
||||
"key": "table",
|
||||
"name": "Historical Telemetry Table",
|
||||
"glyph": "\ue605",
|
||||
"description": "A table for displaying telemetry data",
|
||||
"glyph": "\ue604",
|
||||
"description": "A static table of all values over time for all included telemetry elements. Rows are timestamped data values for each telemetry element; columns are data fields. The number of rows is based on the range of your query. New incoming data must be manually re-queried for.",
|
||||
"priority": 861,
|
||||
"features": "creation",
|
||||
"delegates": [
|
||||
"telemetry"
|
||||
@@ -84,9 +85,9 @@ define([
|
||||
{
|
||||
"key": "rttable",
|
||||
"name": "Real-time Telemetry Table",
|
||||
"glyph": "\ue605",
|
||||
"description": "A table for displaying realtime telemetry" +
|
||||
" data",
|
||||
"glyph": "\ue620",
|
||||
"description": "A scrolling table of latest values for all included telemetry elements. Rows are timestamped data values for each telemetry element; columns are data fields. New incoming data is automatically added to the view.",
|
||||
"priority": 860,
|
||||
"features": "creation",
|
||||
"delegates": [
|
||||
"telemetry"
|
||||
@@ -108,13 +109,13 @@ define([
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "TelemetryTableController",
|
||||
"implementation": TelemetryTableController,
|
||||
"key": "HistoricalTableController",
|
||||
"implementation": HistoricalTableController,
|
||||
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
||||
},
|
||||
{
|
||||
"key": "RTTelemetryTableController",
|
||||
"implementation": RTTelemetryTableController,
|
||||
"key": "RealtimeTableController",
|
||||
"implementation": RealtimeTableController,
|
||||
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
||||
},
|
||||
{
|
||||
@@ -128,8 +129,8 @@ define([
|
||||
{
|
||||
"name": "Historical Table",
|
||||
"key": "table",
|
||||
"glyph": "\ue605",
|
||||
"templateUrl": "templates/table.html",
|
||||
"glyph": "\ue604",
|
||||
"templateUrl": "templates/historical-table.html",
|
||||
"needs": [
|
||||
"telemetry"
|
||||
],
|
||||
@@ -139,7 +140,7 @@ define([
|
||||
{
|
||||
"name": "Real-time Table",
|
||||
"key": "rt-table",
|
||||
"glyph": "\ue605",
|
||||
"glyph": "\ue620",
|
||||
"templateUrl": "templates/rt-table.html",
|
||||
"needs": [
|
||||
"telemetry"
|
||||
@@ -160,6 +161,12 @@ define([
|
||||
"key": "table-options-edit",
|
||||
"templateUrl": "templates/table-options-edit.html"
|
||||
}
|
||||
],
|
||||
"stylesheets": [
|
||||
{
|
||||
"stylesheetUrl": "css/table.css",
|
||||
"priority": "mandatory"
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,33 +19,32 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
.select {
|
||||
@include btnSubtle($colorSelectBg);
|
||||
@if $shdwBtns != none {
|
||||
margin: 0 0 2px 0; // Needed to avoid dropshadow from being clipped by parent containers
|
||||
.sizing-table {
|
||||
min-width: 100%;
|
||||
z-index: -1;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
|
||||
//Add some padding to allow for decorations such as limits indicator
|
||||
td {
|
||||
padding-right: 15px;
|
||||
padding-left: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
padding: 0 $interiorMargin;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
line-height: $formInputH;
|
||||
select {
|
||||
@include appearance(none);
|
||||
box-sizing: border-box;
|
||||
background: none;
|
||||
color: $colorSelectFg;
|
||||
cursor: pointer;
|
||||
border: none !important;
|
||||
padding: 4px 25px 2px 0px;
|
||||
width: 130%;
|
||||
option {
|
||||
margin: $interiorMargin 0; // Firefox
|
||||
}
|
||||
.mct-table {
|
||||
table-layout: fixed;
|
||||
th {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
tbody {
|
||||
tr {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
&:after {
|
||||
@include contextArrow();
|
||||
pointer-events: none;
|
||||
color: rgba($colorSelectFg, percentToDecimal($contrastInvokeMenuPercent));
|
||||
position: absolute;
|
||||
right: $interiorMargin; top: 0;
|
||||
}
|
||||
td {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<div ng-controller="TelemetryTableController">
|
||||
<div ng-controller="HistoricalTableController">
|
||||
<mct-table
|
||||
headers="headers"
|
||||
rows="rows"
|
||||
@@ -1,22 +1,25 @@
|
||||
<div class="l-view-section scrolling"
|
||||
ng-style="overrideRowPositioning ?
|
||||
{'overflow': 'auto'} :
|
||||
{'overflow': 'scroll'}"
|
||||
>
|
||||
<table class="filterable"
|
||||
ng-style="overrideRowPositioning && {
|
||||
<div class="l-view-section scrolling" style="overflow: auto;">
|
||||
<table class="sizing-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td ng-repeat="header in displayHeaders">{{header}}</td>
|
||||
</tr>
|
||||
<tr><td ng-repeat="header in displayHeaders" >
|
||||
{{sizingRow[header].text}}
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="filterable mct-table"
|
||||
ng-style="{
|
||||
height: totalHeight + 'px',
|
||||
'table-layout': overrideRowPositioning ? 'fixed' : 'auto',
|
||||
'max-width': totalWidth
|
||||
}">
|
||||
}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-repeat="header in displayHeaders"
|
||||
ng-style="overrideRowPositioning && {
|
||||
ng-style="{
|
||||
width: columnWidths[$index] + 'px',
|
||||
'max-width': columnWidths[$index] + 'px',
|
||||
overflow: 'none',
|
||||
'box-sizing': 'border-box'
|
||||
}"
|
||||
ng-class="[
|
||||
enableSort ? 'sortable' : '',
|
||||
@@ -29,11 +32,9 @@
|
||||
</tr>
|
||||
<tr ng-if="enableFilter" class="s-filters">
|
||||
<th ng-repeat="header in displayHeaders"
|
||||
ng-style="overrideRowPositioning && {
|
||||
ng-style="{
|
||||
width: columnWidths[$index] + 'px',
|
||||
'max-width': columnWidths[$index] + 'px',
|
||||
overflow: 'none',
|
||||
'box-sizing': 'border-box'
|
||||
}">
|
||||
<input type="text"
|
||||
ng-model="filters[header]"/>
|
||||
@@ -41,21 +42,15 @@
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody ng-style="overrideRowPositioning ? '' : {
|
||||
'opacity': '0.0'
|
||||
}">
|
||||
<tbody>
|
||||
<tr ng-repeat="visibleRow in visibleRows track by visibleRow.rowIndex"
|
||||
ng-style="overrideRowPositioning && {
|
||||
position: 'absolute',
|
||||
ng-style="{
|
||||
top: visibleRow.offsetY + 'px',
|
||||
}">
|
||||
<td ng-repeat="header in displayHeaders"
|
||||
ng-style="overrideRowPositioning && {
|
||||
ng-style=" {
|
||||
width: columnWidths[$index] + 'px',
|
||||
'white-space': 'nowrap',
|
||||
'max-width': columnWidths[$index] + 'px',
|
||||
overflow: 'hidden',
|
||||
'box-sizing': 'border-box'
|
||||
}"
|
||||
class="{{visibleRow.contents[header].cssClass}}">
|
||||
{{ visibleRow.contents[header].text }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div ng-controller="RTTelemetryTableController">
|
||||
<div ng-controller="RealtimeTableController">
|
||||
<mct-table
|
||||
headers="headers"
|
||||
rows="rows"
|
||||
|
||||
@@ -47,7 +47,7 @@ define(
|
||||
* @param metadata Metadata describing the domains and ranges available
|
||||
* @returns {TableConfiguration} This object
|
||||
*/
|
||||
TableConfiguration.prototype.buildColumns = function (metadata) {
|
||||
TableConfiguration.prototype.populateColumns = function (metadata) {
|
||||
var self = this;
|
||||
|
||||
this.columns = [];
|
||||
@@ -141,7 +141,7 @@ define(
|
||||
*/
|
||||
TableConfiguration.prototype.defaultColumnConfiguration = function () {
|
||||
return ((this.domainObject.getModel().configuration || {}).table ||
|
||||
{}).columns || {};
|
||||
{}).columns;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -156,6 +156,16 @@ define(
|
||||
});
|
||||
};
|
||||
|
||||
function configChanged(config1, config2) {
|
||||
var config1Keys = Object.keys(config1),
|
||||
config2Keys = Object.keys(config2);
|
||||
|
||||
return (config1Keys.length !== config2Keys.length) ||
|
||||
config1Keys.some(function(key){
|
||||
return config1[key] !== config2[key];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* As part of the process of building the table definition, extract
|
||||
* configuration from column definitions.
|
||||
@@ -163,10 +173,10 @@ define(
|
||||
* pairs where the key is the column title, and the value is a
|
||||
* boolean indicating whether the column should be shown.
|
||||
*/
|
||||
TableConfiguration.prototype.getColumnConfiguration = function () {
|
||||
TableConfiguration.prototype.buildColumnConfiguration = function () {
|
||||
var configuration = {},
|
||||
//Use existing persisted config, or default it
|
||||
defaultConfig = this.defaultColumnConfiguration();
|
||||
defaultConfig = this.defaultColumnConfiguration() || {};
|
||||
|
||||
/**
|
||||
* For each column header, define a configuration value
|
||||
@@ -175,10 +185,15 @@ define(
|
||||
*/
|
||||
this.getHeaders().forEach(function (columnTitle) {
|
||||
configuration[columnTitle] =
|
||||
typeof defaultConfig[columnTitle] === 'undefined' ? true :
|
||||
typeof (defaultConfig || {})[columnTitle] === 'undefined' ? true :
|
||||
defaultConfig[columnTitle];
|
||||
});
|
||||
|
||||
//Synchronize column configuration with model
|
||||
if (configChanged(configuration, defaultConfig)) {
|
||||
this.saveColumnConfiguration(configuration);
|
||||
}
|
||||
|
||||
return configuration;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[
|
||||
'./TelemetryTableController'
|
||||
],
|
||||
function (TableController) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Extends TelemetryTableController and adds real-time streaming
|
||||
* support.
|
||||
* @memberof platform/features/table
|
||||
* @param $scope
|
||||
* @param telemetryHandler
|
||||
* @param telemetryFormatter
|
||||
* @constructor
|
||||
*/
|
||||
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter) {
|
||||
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
|
||||
}
|
||||
|
||||
HistoricalTableController.prototype = Object.create(TableController.prototype);
|
||||
|
||||
/**
|
||||
* Populates historical data on scope when it becomes available from
|
||||
* the telemetry API
|
||||
*/
|
||||
HistoricalTableController.prototype.addHistoricalData = function () {
|
||||
var rowData = [],
|
||||
self = this;
|
||||
|
||||
this.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
||||
var series = self.handle.getSeries(telemetryObject) || {},
|
||||
pointCount = series.getPointCount ? series.getPointCount() : 0,
|
||||
i = 0;
|
||||
|
||||
for (; i < pointCount; i++) {
|
||||
rowData.push(self.table.getRowValues(telemetryObject,
|
||||
self.handle.makeDatum(telemetryObject, series, i)));
|
||||
}
|
||||
});
|
||||
|
||||
this.$scope.rows = rowData;
|
||||
};
|
||||
|
||||
return HistoricalTableController;
|
||||
}
|
||||
);
|
||||
@@ -23,10 +23,13 @@ define(
|
||||
this.maxDisplayRows = 50;
|
||||
|
||||
this.scrollable = element.find('div');
|
||||
this.thead = element.find('thead');
|
||||
this.tbody = element.find('tbody');
|
||||
this.$scope.sizingRow = {};
|
||||
|
||||
this.scrollable.on('scroll', this.onScroll.bind(this));
|
||||
|
||||
$scope.visibleRows = [];
|
||||
$scope.overrideRowPositioning = false;
|
||||
|
||||
/**
|
||||
* Set default values for optional parameters on a given scope
|
||||
@@ -58,22 +61,23 @@ define(
|
||||
$scope.sortColumn = undefined;
|
||||
$scope.sortDirection = undefined;
|
||||
}
|
||||
self.updateRows($scope.rows);
|
||||
self.setRows($scope.rows);
|
||||
};
|
||||
|
||||
/*
|
||||
* Define watches to listen for changes to headers and rows.
|
||||
*/
|
||||
$scope.$watchCollection('filters', function () {
|
||||
self.updateRows($scope.rows);
|
||||
self.setRows($scope.rows);
|
||||
});
|
||||
$scope.$watch('headers', this.updateHeaders.bind(this));
|
||||
$scope.$watch('rows', this.updateRows.bind(this));
|
||||
$scope.$watch('headers', this.setHeaders.bind(this));
|
||||
$scope.$watch('rows', this.setRows.bind(this));
|
||||
|
||||
/*
|
||||
* Listen for rows added individually (eg. for real-time tables)
|
||||
*/
|
||||
$scope.$on('add:row', this.newRow.bind(this));
|
||||
$scope.$on('add:row', this.addRow.bind(this));
|
||||
$scope.$on('remove:row', this.removeRow.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,18 +99,38 @@ define(
|
||||
|
||||
/**
|
||||
* Handles a row add event. Rows can be added as needed using the
|
||||
* `addRow` broadcast event.
|
||||
* `add:row` broadcast event.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.newRow = function (event, rowIndex) {
|
||||
MCTTableController.prototype.addRow = function (event, rowIndex) {
|
||||
var row = this.$scope.rows[rowIndex];
|
||||
//Add row to the filtered, sorted list of all rows
|
||||
if (this.filterRows([row]).length > 0) {
|
||||
this.insertSorted(this.$scope.displayRows, row);
|
||||
}
|
||||
|
||||
this.$timeout(this.setElementSizes.bind(this))
|
||||
.then(this.scrollToBottom.bind(this));
|
||||
//Does the row pass the current filter?
|
||||
if (this.filterRows([row]).length === 1) {
|
||||
//Insert the row into the correct position in the array
|
||||
this.insertSorted(this.$scope.displayRows, row);
|
||||
|
||||
//Resize the columns , then update the rows visible in the table
|
||||
this.resize([this.$scope.sizingRow, row])
|
||||
.then(this.setVisibleRows.bind(this))
|
||||
.then(this.scrollToBottom.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a row remove event. Rows can be removed as needed using the
|
||||
* `remove:row` broadcast event.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.removeRow = function (event, rowIndex) {
|
||||
var row = this.$scope.rows[rowIndex],
|
||||
// Do a sequential search here. Only way of finding row is by
|
||||
// object equality, so array is in effect unsorted.
|
||||
indexInDisplayRows = this.$scope.displayRows.indexOf(row);
|
||||
if (indexInDisplayRows != -1) {
|
||||
this.$scope.displayRows.splice(indexInDisplayRows, 1);
|
||||
this.setVisibleRows();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -208,7 +232,7 @@ define(
|
||||
* enabled, reset filters. If sorting is enabled, reset
|
||||
* sorting.
|
||||
*/
|
||||
MCTTableController.prototype.updateHeaders = function (newHeaders) {
|
||||
MCTTableController.prototype.setHeaders = function (newHeaders) {
|
||||
if (!newHeaders){
|
||||
return;
|
||||
}
|
||||
@@ -224,7 +248,7 @@ define(
|
||||
this.$scope.sortColumn = undefined;
|
||||
this.$scope.sortDirection = undefined;
|
||||
}
|
||||
this.updateRows(this.$scope.rows);
|
||||
this.setRows(this.$scope.rows);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -232,13 +256,12 @@ define(
|
||||
* for individual rows.
|
||||
*/
|
||||
MCTTableController.prototype.setElementSizes = function () {
|
||||
var self = this,
|
||||
thead = this.element.find('thead'),
|
||||
tbody = this.element.find('tbody'),
|
||||
var thead = this.thead,
|
||||
tbody = this.tbody,
|
||||
firstRow = tbody.find('tr'),
|
||||
column = firstRow.find('td'),
|
||||
headerHeight = thead.prop('offsetHeight'),
|
||||
rowHeight = 20,
|
||||
rowHeight = firstRow.prop('offsetHeight'),
|
||||
columnWidth,
|
||||
tableWidth = 0,
|
||||
overallHeight = headerHeight + (rowHeight *
|
||||
@@ -255,15 +278,27 @@ define(
|
||||
this.$scope.headerHeight = headerHeight;
|
||||
this.$scope.rowHeight = rowHeight;
|
||||
this.$scope.totalHeight = overallHeight;
|
||||
this.setVisibleRows();
|
||||
|
||||
if (tableWidth > 0) {
|
||||
this.$scope.totalWidth = tableWidth + 'px';
|
||||
} else {
|
||||
this.$scope.totalWidth = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
this.$scope.overrideRowPositioning = true;
|
||||
/**
|
||||
* Given a value, if it is a number, or a string representation of a
|
||||
* number, then return a number representation. Otherwise, return
|
||||
* the original value. It's a little more robust than using just
|
||||
* Number() or parseFloat, or isNaN in isolation, all of which are
|
||||
* fairly inconsistent in their results.
|
||||
* @param value The value to return as a number.
|
||||
* @returns {*} The value cast to a Number, or the original value if
|
||||
* a Number representation is not possible.
|
||||
*/
|
||||
MCTTableController.prototype.toNumber = function (value){
|
||||
var val = !isNaN(Number(value)) && !isNaN(parseFloat(value)) ? Number(value) : value;
|
||||
return val;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -282,12 +317,8 @@ define(
|
||||
return min; // Element is not in array, min gives direction
|
||||
}
|
||||
|
||||
valA = isNaN(searchElement[sortKey].text) ?
|
||||
searchElement[sortKey].text :
|
||||
parseFloat(searchElement[sortKey].text);
|
||||
valB = isNaN(searchArray[sampleAt][sortKey].text) ?
|
||||
searchArray[sampleAt][sortKey].text :
|
||||
searchArray[sampleAt][sortKey].text;
|
||||
valA = self.toNumber(searchElement[sortKey].text);
|
||||
valB = self.toNumber(searchArray[sampleAt][sortKey].text);
|
||||
|
||||
switch(self.sortComparator(valA, valB)) {
|
||||
case -1:
|
||||
@@ -368,10 +399,9 @@ define(
|
||||
//If the values to compare can be compared as
|
||||
// numbers, do so. String comparison of number
|
||||
// values can cause inconsistencies
|
||||
var valA = isNaN(a[sortKey].text) ? a[sortKey].text :
|
||||
parseFloat(a[sortKey].text),
|
||||
valB = isNaN(b[sortKey].text) ? b[sortKey].text :
|
||||
parseFloat(b[sortKey].text);
|
||||
|
||||
var valA = self.toNumber(a[sortKey].text),
|
||||
valB = self.toNumber(b[sortKey].text);
|
||||
|
||||
return self.sortComparator(valA, valB);
|
||||
});
|
||||
@@ -383,74 +413,51 @@ define(
|
||||
* pre-calculate optimal column sizes without having to render
|
||||
* every row.
|
||||
*/
|
||||
MCTTableController.prototype.findLargestRow = function (rows) {
|
||||
var largestRow = rows.reduce(function (largestRow, row) {
|
||||
MCTTableController.prototype.buildLargestRow = function (rows) {
|
||||
var largestRow = rows.reduce(function (prevLargest, row) {
|
||||
Object.keys(row).forEach(function (key) {
|
||||
var currentColumn = row[key].text,
|
||||
var currentColumn,
|
||||
currentColumnLength,
|
||||
largestColumn,
|
||||
largestColumnLength;
|
||||
if (!row[key]){
|
||||
//do nothing, no value for this column;
|
||||
} else {
|
||||
currentColumn = (row[key]).text;
|
||||
currentColumnLength =
|
||||
(currentColumn && currentColumn.length) ?
|
||||
currentColumn.length :
|
||||
currentColumn,
|
||||
largestColumn = largestRow[key].text,
|
||||
largestColumnLength =
|
||||
(largestColumn && largestColumn.length) ?
|
||||
largestColumn.length :
|
||||
largestColumn;
|
||||
currentColumn;
|
||||
largestColumn = prevLargest[key] ? prevLargest[key].text : "";
|
||||
largestColumnLength = largestColumn.length;
|
||||
|
||||
if (currentColumnLength > largestColumnLength) {
|
||||
largestRow[key] = JSON.parse(JSON.stringify(row[key]));
|
||||
if (currentColumnLength > largestColumnLength) {
|
||||
prevLargest[key] = JSON.parse(JSON.stringify(row[key]));
|
||||
}
|
||||
}
|
||||
});
|
||||
return largestRow;
|
||||
return prevLargest;
|
||||
}, JSON.parse(JSON.stringify(rows[0] || {})));
|
||||
|
||||
largestRow = JSON.parse(JSON.stringify(largestRow));
|
||||
|
||||
// Pad with characters to accomodate variable-width fonts,
|
||||
// and remove characters that would allow word-wrapping.
|
||||
Object.keys(largestRow).forEach(function (key) {
|
||||
var padCharacters,
|
||||
i;
|
||||
|
||||
largestRow[key].text = String(largestRow[key].text);
|
||||
padCharacters = largestRow[key].text.length / 10;
|
||||
for (i = 0; i < padCharacters; i++) {
|
||||
largestRow[key].text = largestRow[key].text + 'W';
|
||||
}
|
||||
largestRow[key].text = largestRow[key].text
|
||||
.replace(/[ \-_]/g, 'W');
|
||||
});
|
||||
return largestRow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the widest row in the table, pads that row, and adds
|
||||
* it to the table. Allows the table to size itself, then uses this
|
||||
* as basis for column dimensions.
|
||||
* Calculates the widest row in the table, and if necessary, resizes
|
||||
* the table accordingly
|
||||
*
|
||||
* @param rows the rows on which to resize
|
||||
* @returns {Promise} a promise that will resolve when resizing has
|
||||
* occurred.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.resize = function (){
|
||||
var largestRow = this.findLargestRow(this.$scope.displayRows),
|
||||
self = this;
|
||||
this.$scope.visibleRows = [
|
||||
{
|
||||
rowIndex: 0,
|
||||
offsetY: undefined,
|
||||
contents: largestRow
|
||||
}
|
||||
];
|
||||
MCTTableController.prototype.resize = function (rows){
|
||||
|
||||
//Wait a timeout to allow digest of previous change to visible
|
||||
// rows to happen.
|
||||
this.$timeout(function () {
|
||||
//Remove temporary padding row used for setting column widths
|
||||
self.$scope.visibleRows = [];
|
||||
self.setElementSizes();
|
||||
});
|
||||
this.$scope.sizingRow = this.buildLargestRow(rows);
|
||||
return this.$timeout(this.setElementSizes.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* @priate
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.filterAndSort = function (rows) {
|
||||
var displayRows = rows;
|
||||
@@ -468,19 +475,14 @@ define(
|
||||
* Update rows with new data. If filtering is enabled, rows
|
||||
* will be sorted before display.
|
||||
*/
|
||||
MCTTableController.prototype.updateRows = function (newRows) {
|
||||
//Reset visible rows because new row data available.
|
||||
this.$scope.visibleRows = [];
|
||||
|
||||
this.$scope.overrideRowPositioning = false;
|
||||
|
||||
MCTTableController.prototype.setRows = function (newRows) {
|
||||
//Nothing to show because no columns visible
|
||||
if (!this.$scope.displayHeaders) {
|
||||
if (!this.$scope.displayHeaders || !newRows) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.filterAndSort(newRows || []);
|
||||
this.resize();
|
||||
this.resize(newRows).then(this.setVisibleRows.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,10 +37,11 @@ define(
|
||||
* @param telemetryFormatter
|
||||
* @constructor
|
||||
*/
|
||||
function RTTelemetryTableController($scope, telemetryHandler, telemetryFormatter) {
|
||||
function RealtimeTableController($scope, telemetryHandler, telemetryFormatter) {
|
||||
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
|
||||
|
||||
$scope.autoScroll = false;
|
||||
this.maxRows = 100000;
|
||||
|
||||
/*
|
||||
* Determine if auto-scroll should be enabled. Is enabled
|
||||
@@ -65,52 +66,35 @@ define(
|
||||
});
|
||||
}
|
||||
|
||||
RTTelemetryTableController.prototype = Object.create(TableController.prototype);
|
||||
RealtimeTableController.prototype = Object.create(TableController.prototype);
|
||||
|
||||
/**
|
||||
Override the subscribe function defined on the parent controller in
|
||||
order to handle realtime telemetry instead of historical.
|
||||
* Overrides method on TelemetryTableController providing handling
|
||||
* for realtime data.
|
||||
*/
|
||||
RTTelemetryTableController.prototype.subscribe = function () {
|
||||
var self = this;
|
||||
self.$scope.rows = undefined;
|
||||
(this.subscriptions || []).forEach(function (unsubscribe){
|
||||
unsubscribe();
|
||||
});
|
||||
RealtimeTableController.prototype.addRealtimeData = function() {
|
||||
var self = this,
|
||||
datum,
|
||||
row;
|
||||
this.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
||||
datum = self.handle.getDatum(telemetryObject);
|
||||
if (datum) {
|
||||
//Populate row values from telemetry datum
|
||||
row = self.table.getRowValues(telemetryObject, datum);
|
||||
self.$scope.rows.push(row);
|
||||
|
||||
if (this.handle) {
|
||||
this.handle.unsubscribe();
|
||||
}
|
||||
|
||||
function updateData(){
|
||||
var datum,
|
||||
row;
|
||||
self.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
||||
datum = self.handle.getDatum(telemetryObject);
|
||||
if (datum) {
|
||||
row = self.table.getRowValues(telemetryObject, datum);
|
||||
if (!self.$scope.rows){
|
||||
self.$scope.rows = [row];
|
||||
self.$scope.$digest();
|
||||
} else {
|
||||
self.$scope.rows.push(row);
|
||||
self.$scope.$broadcast('add:row',
|
||||
self.$scope.rows.length - 1);
|
||||
}
|
||||
//Inform table that a new row has been added
|
||||
if (self.$scope.rows.length > self.maxRows) {
|
||||
self.$scope.$broadcast('remove:row', 0);
|
||||
self.$scope.rows.shift();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
|
||||
this.$scope.domainObject,
|
||||
updateData,
|
||||
true // Lossless
|
||||
);
|
||||
|
||||
this.setup();
|
||||
self.$scope.$broadcast('add:row',
|
||||
self.$scope.rows.length - 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return RTTelemetryTableController;
|
||||
return RealtimeTableController;
|
||||
}
|
||||
);
|
||||
@@ -51,13 +51,29 @@ define(
|
||||
|
||||
this.$scope = $scope;
|
||||
this.domainObject = $scope.domainObject;
|
||||
this.listeners = [];
|
||||
|
||||
$scope.columnsForm = {};
|
||||
|
||||
this.domainObject.getCapability('mutation').listen(function (model) {
|
||||
self.populateForm(model);
|
||||
function unlisten() {
|
||||
self.listeners.forEach(function (listener) {
|
||||
listener();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.$watch('domainObject', function(domainObject) {
|
||||
unlisten();
|
||||
self.populateForm(domainObject.getModel());
|
||||
|
||||
self.listeners.push(self.domainObject.getCapability('mutation').listen(function (model) {
|
||||
self.populateForm(model);
|
||||
}));
|
||||
});
|
||||
|
||||
/**
|
||||
* Maintain a configuration object on scope that stores column
|
||||
* configuration. On change, synchronize with object model.
|
||||
*/
|
||||
$scope.$watchCollection('configuration.table.columns', function (columns){
|
||||
if (columns){
|
||||
self.domainObject.useCapability('mutation', function (model) {
|
||||
@@ -67,6 +83,11 @@ define(
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Destroy all mutation listeners
|
||||
*/
|
||||
$scope.$on('$destroy', unlisten);
|
||||
|
||||
}
|
||||
|
||||
TableOptionsController.prototype.populateForm = function (model) {
|
||||
@@ -86,7 +107,7 @@ define(
|
||||
'key': key
|
||||
});
|
||||
});
|
||||
this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration));
|
||||
this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration || {}));
|
||||
};
|
||||
|
||||
return TableOptionsController;
|
||||
|
||||
@@ -52,19 +52,15 @@ define(
|
||||
this.$scope = $scope;
|
||||
this.columns = {}; //Range and Domain columns
|
||||
this.handle = undefined;
|
||||
//this.pending = false;
|
||||
this.telemetryHandler = telemetryHandler;
|
||||
this.table = new TableConfiguration($scope.domainObject,
|
||||
telemetryFormatter);
|
||||
this.changeListeners = [];
|
||||
|
||||
$scope.rows = undefined;
|
||||
$scope.rows = [];
|
||||
|
||||
// Subscribe to telemetry when a domain object becomes available
|
||||
this.$scope.$watch('domainObject', function(domainObject){
|
||||
if (!domainObject)
|
||||
return;
|
||||
|
||||
this.$scope.$watch('domainObject', function(){
|
||||
self.subscribe();
|
||||
self.registerChangeListeners();
|
||||
});
|
||||
@@ -73,16 +69,24 @@ define(
|
||||
this.$scope.$on("$destroy", this.destroy.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryTableController.prototype.unregisterChangeListeners = function () {
|
||||
this.changeListeners.forEach(function (listener) {
|
||||
return listener && listener();
|
||||
});
|
||||
this.changeListeners = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Defer registration of change listeners until domain object is
|
||||
* available in order to avoid race conditions
|
||||
* @private
|
||||
*/
|
||||
TelemetryTableController.prototype.registerChangeListeners = function () {
|
||||
this.changeListeners.forEach(function (listener) {
|
||||
return listener && listener();
|
||||
});
|
||||
this.changeListeners = [];
|
||||
this.unregisterChangeListeners();
|
||||
|
||||
// When composition changes, re-subscribe to the various
|
||||
// telemetry subscriptions
|
||||
this.changeListeners.push(this.$scope.$watchCollection(
|
||||
@@ -103,25 +107,37 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Function for handling realtime data when it is available. This
|
||||
* will be called by the telemetry framework when new data is
|
||||
* available.
|
||||
*
|
||||
* Method should be overridden by specializing class.
|
||||
*/
|
||||
TelemetryTableController.prototype.addRealtimeData = function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* Function for handling historical data. Will be called by
|
||||
* telemetry framework when requested historical data is available.
|
||||
* Should be overridden by specializing class.
|
||||
*/
|
||||
TelemetryTableController.prototype.addHistoricalData = function () {
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new subscription. This can be overridden by children to
|
||||
change default behaviour (which is to retrieve historical telemetry
|
||||
only).
|
||||
*/
|
||||
TelemetryTableController.prototype.subscribe = function () {
|
||||
var self = this;
|
||||
|
||||
if (this.handle) {
|
||||
this.handle.unsubscribe();
|
||||
}
|
||||
|
||||
//Noop because not supporting realtime data right now
|
||||
function noop(){
|
||||
}
|
||||
|
||||
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
|
||||
this.$scope.domainObject,
|
||||
noop,
|
||||
this.addRealtimeData.bind(this),
|
||||
true // Lossless
|
||||
);
|
||||
|
||||
@@ -130,28 +146,6 @@ define(
|
||||
this.setup();
|
||||
};
|
||||
|
||||
/**
|
||||
* Populates historical data on scope when it becomes available
|
||||
* @private
|
||||
*/
|
||||
TelemetryTableController.prototype.addHistoricalData = function () {
|
||||
var rowData = [],
|
||||
self = this;
|
||||
|
||||
this.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
||||
var series = self.handle.getSeries(telemetryObject) || {},
|
||||
pointCount = series.getPointCount ? series.getPointCount() : 0,
|
||||
i = 0;
|
||||
|
||||
for (; i < pointCount; i++) {
|
||||
rowData.push(self.table.getRowValues(telemetryObject,
|
||||
self.handle.makeDatum(telemetryObject, series, i)));
|
||||
}
|
||||
});
|
||||
|
||||
this.$scope.rows = rowData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup table columns based on domain object metadata
|
||||
*/
|
||||
@@ -162,7 +156,9 @@ define(
|
||||
|
||||
if (handle) {
|
||||
handle.promiseTelemetryObjects().then(function () {
|
||||
table.buildColumns(handle.getMetadata());
|
||||
self.$scope.headers = [];
|
||||
self.$scope.rows = [];
|
||||
table.populateColumns(handle.getMetadata());
|
||||
|
||||
self.filterColumns();
|
||||
|
||||
@@ -176,26 +172,14 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param object The object for which data is available (table may
|
||||
* be composed of multiple objects)
|
||||
* @param datum The data received from the telemetry source
|
||||
*/
|
||||
TelemetryTableController.prototype.updateRows = function (object, datum) {
|
||||
this.$scope.rows.push(this.table.getRowValues(object, datum));
|
||||
};
|
||||
|
||||
/**
|
||||
* When column configuration changes, update the visible headers
|
||||
* accordingly.
|
||||
* @private
|
||||
*/
|
||||
TelemetryTableController.prototype.filterColumns = function (columnConfig) {
|
||||
if (!columnConfig){
|
||||
columnConfig = this.table.getColumnConfiguration();
|
||||
this.table.saveColumnConfiguration(columnConfig);
|
||||
}
|
||||
TelemetryTableController.prototype.filterColumns = function () {
|
||||
var columnConfig = this.table.buildColumnConfiguration();
|
||||
|
||||
//Populate headers with visible columns (determined by configuration)
|
||||
this.$scope.headers = Object.keys(columnConfig).filter(function (column) {
|
||||
return columnConfig[column];
|
||||
|
||||
@@ -12,6 +12,51 @@ define(
|
||||
* Defines a generic 'Table' component. The table can be populated
|
||||
* en-masse by setting the rows attribute, or rows can be added as
|
||||
* needed via a broadcast 'addRow' event.
|
||||
*
|
||||
* This directive accepts parameters specifying header and row
|
||||
* content, as well as some additional options.
|
||||
*
|
||||
* Two broadcast events for notifying the table that the rows have
|
||||
* changed. For performance reasons, the table does not monitor the
|
||||
* content of `rows` constantly.
|
||||
* - 'add:row': A $broadcast event that will notify the table that
|
||||
* a new row has been added to the table.
|
||||
* eg.
|
||||
* <pre><code>
|
||||
* $scope.rows.push(newRow);
|
||||
* $scope.$broadcast('add:row', $scope.rows.length-1);
|
||||
* </code></pre>
|
||||
* The code above adds a new row, and alerts the table using the
|
||||
* add:row event. Sorting and filtering will be applied
|
||||
* automatically by the table component.
|
||||
*
|
||||
* - 'remove:row': A $broadcast event that will notify the table that a
|
||||
* row should be removed from the table.
|
||||
* eg.
|
||||
* <pre><code>
|
||||
* $scope.rows.slice(5, 1);
|
||||
* $scope.$broadcast('remove:row', 5);
|
||||
* </code></pre>
|
||||
* The code above removes a row from the rows array, and then alerts
|
||||
* the table to its removal.
|
||||
*
|
||||
* @memberof platform/features/table
|
||||
* @param {string[]} headers The column titles to appear at the top
|
||||
* of the table. Corresponding values are specified in the rows
|
||||
* using the header title provided here.
|
||||
* @param {Object[]} rows The row content. Each row is an object
|
||||
* with key-value pairs where the key corresponds to a header
|
||||
* specified in the headers parameter.
|
||||
* @param {boolean} enableFilter If true, values will be searchable
|
||||
* and results filtered
|
||||
* @param {boolean} enableSort If true, sorting will be enabled
|
||||
* allowing sorting by clicking on column headers
|
||||
* @param {boolean} autoScroll If true, table will automatically
|
||||
* scroll to the bottom as new data arrives. Auto-scroll can be
|
||||
* disengaged manually by scrolling away from the bottom of the
|
||||
* table, and can also be enabled manually by scrolling to the bottom of
|
||||
* the table rows.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MCTTable($timeout) {
|
||||
|
||||
@@ -116,10 +116,10 @@ define(
|
||||
}];
|
||||
|
||||
beforeEach(function() {
|
||||
table.buildColumns(metadata);
|
||||
table.populateColumns(metadata);
|
||||
});
|
||||
|
||||
it("populates the columns attribute", function() {
|
||||
it("populates columns", function() {
|
||||
expect(table.columns.length).toBe(5);
|
||||
});
|
||||
|
||||
@@ -141,7 +141,7 @@ define(
|
||||
|
||||
it("Provides a default configuration with all columns" +
|
||||
" visible", function() {
|
||||
var configuration = table.getColumnConfiguration();
|
||||
var configuration = table.buildColumnConfiguration();
|
||||
|
||||
expect(configuration).toBeDefined();
|
||||
expect(Object.keys(configuration).every(function(key){
|
||||
@@ -160,7 +160,7 @@ define(
|
||||
};
|
||||
mockModel.configuration = modelConfig;
|
||||
|
||||
tableConfig = table.getColumnConfiguration();
|
||||
tableConfig = table.buildColumnConfiguration();
|
||||
|
||||
expect(tableConfig).toBeDefined();
|
||||
expect(tableConfig['Range 1']).toBe(false);
|
||||
@@ -191,6 +191,9 @@ define(
|
||||
expect(mockTelemetryFormatter.formatRangeValue).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
/**
|
||||
* TODO: Add test for saving column config
|
||||
*/
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
define(
|
||||
[
|
||||
"../../src/controllers/TelemetryTableController"
|
||||
"../../src/controllers/HistoricalTableController"
|
||||
],
|
||||
function (TableController) {
|
||||
"use strict";
|
||||
@@ -73,14 +73,14 @@ define(
|
||||
|
||||
mockTable = jasmine.createSpyObj('table',
|
||||
[
|
||||
'buildColumns',
|
||||
'getColumnConfiguration',
|
||||
'populateColumns',
|
||||
'buildColumnConfiguration',
|
||||
'getRowValues',
|
||||
'saveColumnConfiguration'
|
||||
]
|
||||
);
|
||||
mockTable.columns = [];
|
||||
mockTable.getColumnConfiguration.andReturn(mockConfiguration);
|
||||
mockTable.buildColumnConfiguration.andReturn(mockConfiguration);
|
||||
|
||||
mockDomainObject= jasmine.createSpyObj('domainObject', [
|
||||
'getCapability',
|
||||
@@ -126,21 +126,18 @@ define(
|
||||
expect(mockTelemetryHandle.unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('the controller makes use of the table', function () {
|
||||
describe('makes use of the table', function () {
|
||||
|
||||
it('to create column definitions from telemetry' +
|
||||
' metadata', function () {
|
||||
controller.setup();
|
||||
expect(mockTable.buildColumns).toHaveBeenCalled();
|
||||
expect(mockTable.populateColumns).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('to create column configuration, which is written to the' +
|
||||
' object model', function () {
|
||||
var mockModel = {};
|
||||
|
||||
controller.setup();
|
||||
expect(mockTable.getColumnConfiguration).toHaveBeenCalled();
|
||||
expect(mockTable.saveColumnConfiguration).toHaveBeenCalled();
|
||||
expect(mockTable.buildColumnConfiguration).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,15 +58,18 @@ define(
|
||||
|
||||
mockElement = jasmine.createSpyObj('element', [
|
||||
'find',
|
||||
'prop',
|
||||
'on'
|
||||
]);
|
||||
mockElement.find.andReturn(mockElement);
|
||||
mockElement.prop.andReturn(0);
|
||||
|
||||
mockScope.displayHeaders = true;
|
||||
mockTimeout = jasmine.createSpy('$timeout');
|
||||
mockTimeout.andReturn(promise(undefined));
|
||||
|
||||
controller = new MCTTableController(mockScope, mockTimeout, mockElement);
|
||||
spyOn(controller, 'setVisibleRows');
|
||||
});
|
||||
|
||||
it('Reacts to changes to filters, headers, and rows', function() {
|
||||
@@ -115,25 +118,34 @@ define(
|
||||
});
|
||||
|
||||
it('Sets rows on scope when rows change', function() {
|
||||
controller.updateRows(testRows);
|
||||
controller.setRows(testRows);
|
||||
expect(mockScope.displayRows.length).toBe(3);
|
||||
expect(mockScope.displayRows).toEqual(testRows);
|
||||
});
|
||||
|
||||
it('Supports adding rows individually', function() {
|
||||
var addRowFunc = mockScope.$on.mostRecentCall.args[1],
|
||||
var addRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-2].args[1],
|
||||
row4 = {
|
||||
'col1': {'text': 'row3 col1'},
|
||||
'col2': {'text': 'ghi'},
|
||||
'col3': {'text': 'row3 col3'}
|
||||
};
|
||||
controller.updateRows(testRows);
|
||||
controller.setRows(testRows);
|
||||
expect(mockScope.displayRows.length).toBe(3);
|
||||
testRows.push(row4);
|
||||
addRowFunc(3);
|
||||
addRowFunc(undefined, 3);
|
||||
expect(mockScope.displayRows.length).toBe(4);
|
||||
});
|
||||
|
||||
it('Supports removing rows individually', function() {
|
||||
var removeRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-1].args[1];
|
||||
controller.setRows(testRows);
|
||||
expect(mockScope.displayRows.length).toBe(3);
|
||||
removeRowFunc(undefined, 2);
|
||||
expect(mockScope.displayRows.length).toBe(2);
|
||||
expect(controller.setVisibleRows).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('sorting', function() {
|
||||
var sortedRows;
|
||||
|
||||
@@ -168,6 +180,50 @@ define(
|
||||
expect(sortedRows[2].col2.text).toEqual('abc');
|
||||
});
|
||||
|
||||
it('converts number strings to numbers', function () {
|
||||
var val1 = "",
|
||||
val2 = "1",
|
||||
val3 = "2016-04-05 18:41:30.713Z",
|
||||
val4 = "1.1",
|
||||
val5 = "8.945520958175627e-13";
|
||||
|
||||
expect(controller.toNumber(val1)).toEqual("");
|
||||
expect(controller.toNumber(val2)).toEqual(1);
|
||||
expect(controller.toNumber(val3)).toEqual("2016-04-05 18:41:30.713Z");
|
||||
expect(controller.toNumber(val4)).toEqual(1.1);
|
||||
expect(controller.toNumber(val5)).toEqual(8.945520958175627e-13);
|
||||
});
|
||||
|
||||
it('correctly sorts rows of differing types', function () {
|
||||
mockScope.sortColumn = 'col2';
|
||||
mockScope.sortDirection = 'desc';
|
||||
|
||||
testRows.push({
|
||||
'col1': {'text': 'row4 col1'},
|
||||
'col2': {'text': '123'},
|
||||
'col3': {'text': 'row4 col3'}
|
||||
});
|
||||
testRows.push({
|
||||
'col1': {'text': 'row5 col1'},
|
||||
'col2': {'text': '456'},
|
||||
'col3': {'text': 'row5 col3'}
|
||||
});
|
||||
testRows.push({
|
||||
'col1': {'text': 'row5 col1'},
|
||||
'col2': {'text': ''},
|
||||
'col3': {'text': 'row5 col3'}
|
||||
});
|
||||
|
||||
sortedRows = controller.sortRows(testRows);
|
||||
expect(sortedRows[0].col2.text).toEqual('ghi');
|
||||
expect(sortedRows[1].col2.text).toEqual('def');
|
||||
expect(sortedRows[2].col2.text).toEqual('abc');
|
||||
|
||||
expect(sortedRows[sortedRows.length-3].col2.text).toEqual('456');
|
||||
expect(sortedRows[sortedRows.length-2].col2.text).toEqual('123');
|
||||
expect(sortedRows[sortedRows.length-1].col2.text).toEqual('');
|
||||
});
|
||||
|
||||
describe('Adding new rows', function() {
|
||||
var row4,
|
||||
row5,
|
||||
@@ -199,20 +255,20 @@ define(
|
||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||
|
||||
mockScope.rows.push(row4);
|
||||
controller.newRow(undefined, mockScope.rows.length-1);
|
||||
controller.addRow(undefined, mockScope.rows.length-1);
|
||||
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
||||
|
||||
mockScope.rows.push(row5);
|
||||
controller.newRow(undefined, mockScope.rows.length-1);
|
||||
controller.addRow(undefined, mockScope.rows.length-1);
|
||||
expect(mockScope.displayRows[4].col2.text).toEqual('aaa');
|
||||
|
||||
mockScope.rows.push(row6);
|
||||
controller.newRow(undefined, mockScope.rows.length-1);
|
||||
controller.addRow(undefined, mockScope.rows.length-1);
|
||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
||||
|
||||
//Add a duplicate row
|
||||
mockScope.rows.push(row6);
|
||||
controller.newRow(undefined, mockScope.rows.length-1);
|
||||
controller.addRow(undefined, mockScope.rows.length-1);
|
||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
||||
});
|
||||
@@ -228,18 +284,18 @@ define(
|
||||
mockScope.displayRows = controller.filterRows(testRows);
|
||||
|
||||
mockScope.rows.push(row5);
|
||||
controller.newRow(undefined, mockScope.rows.length-1);
|
||||
controller.addRow(undefined, mockScope.rows.length-1);
|
||||
expect(mockScope.displayRows.length).toBe(2);
|
||||
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
|
||||
|
||||
mockScope.rows.push(row6);
|
||||
controller.newRow(undefined, mockScope.rows.length-1);
|
||||
controller.addRow(undefined, mockScope.rows.length-1);
|
||||
expect(mockScope.displayRows.length).toBe(2);
|
||||
//Row was not added because does not match filter
|
||||
});
|
||||
|
||||
it('Adds new rows at the correct sort position when' +
|
||||
' not sorted ', function() {
|
||||
' not sorted ', function () {
|
||||
mockScope.sortColumn = undefined;
|
||||
mockScope.sortDirection = undefined;
|
||||
mockScope.filters = {};
|
||||
@@ -247,14 +303,33 @@ define(
|
||||
mockScope.displayRows = testRows.slice(0);
|
||||
|
||||
mockScope.rows.push(row5);
|
||||
controller.newRow(undefined, mockScope.rows.length-1);
|
||||
controller.addRow(undefined, mockScope.rows.length-1);
|
||||
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
|
||||
|
||||
mockScope.rows.push(row6);
|
||||
controller.newRow(undefined, mockScope.rows.length-1);
|
||||
controller.addRow(undefined, mockScope.rows.length-1);
|
||||
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
||||
});
|
||||
|
||||
it('Resizes columns if length of any columns in new' +
|
||||
' row exceeds corresponding existing column', function() {
|
||||
var row7 = {
|
||||
'col1': {'text': 'row6 col1'},
|
||||
'col2': {'text': 'some longer string'},
|
||||
'col3': {'text': 'row6 col3'}
|
||||
};
|
||||
|
||||
mockScope.sortColumn = undefined;
|
||||
mockScope.sortDirection = undefined;
|
||||
mockScope.filters = {};
|
||||
|
||||
mockScope.displayRows = testRows.slice(0);
|
||||
|
||||
mockScope.rows.push(row7);
|
||||
controller.addRow(undefined, mockScope.rows.length-1);
|
||||
expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
define(
|
||||
[
|
||||
"../../src/controllers/RTTelemetryTableController"
|
||||
"../../src/controllers/RealtimeTableController"
|
||||
],
|
||||
function (TableController) {
|
||||
"use strict";
|
||||
@@ -77,14 +77,14 @@ define(
|
||||
|
||||
mockTable = jasmine.createSpyObj('table',
|
||||
[
|
||||
'buildColumns',
|
||||
'getColumnConfiguration',
|
||||
'populateColumns',
|
||||
'buildColumnConfiguration',
|
||||
'getRowValues',
|
||||
'saveColumnConfiguration'
|
||||
]
|
||||
);
|
||||
mockTable.columns = [];
|
||||
mockTable.getColumnConfiguration.andReturn(mockConfiguration);
|
||||
mockTable.buildColumnConfiguration.andReturn(mockConfiguration);
|
||||
mockTable.getRowValues.andReturn(mockTableRow);
|
||||
|
||||
mockDomainObject= jasmine.createSpyObj('domainObject', [
|
||||
@@ -107,13 +107,16 @@ define(
|
||||
'unsubscribe',
|
||||
'getDatum',
|
||||
'promiseTelemetryObjects',
|
||||
'getTelemetryObjects'
|
||||
'getTelemetryObjects',
|
||||
'request'
|
||||
]);
|
||||
|
||||
// Arbitrary array with non-zero length, contents are not
|
||||
// used by mocks
|
||||
mockTelemetryHandle.getTelemetryObjects.andReturn([{}]);
|
||||
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
|
||||
mockTelemetryHandle.getDatum.andReturn({});
|
||||
mockTelemetryHandle.request.andReturn(promise(undefined));
|
||||
|
||||
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
|
||||
'handle'
|
||||
@@ -130,11 +133,29 @@ define(
|
||||
expect(mockTelemetryHandler.handle).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function), true);
|
||||
});
|
||||
|
||||
it('updates table with new streaming telemetry', function () {
|
||||
controller.subscribe();
|
||||
mockScope.rows = [];
|
||||
mockTelemetryHandler.handle.mostRecentCall.args[1]();
|
||||
expect(mockScope.$broadcast).toHaveBeenCalledWith('add:row', 0);
|
||||
describe('receives new telemetry', function () {
|
||||
beforeEach(function() {
|
||||
controller.subscribe();
|
||||
mockScope.rows = [];
|
||||
});
|
||||
|
||||
it('updates table with new streaming telemetry', function () {
|
||||
mockTelemetryHandler.handle.mostRecentCall.args[1]();
|
||||
expect(mockScope.$broadcast).toHaveBeenCalledWith('add:row', 0);
|
||||
});
|
||||
it('observes the row limit', function () {
|
||||
var i = 0;
|
||||
controller.maxRows = 10;
|
||||
|
||||
//Fill rows array with elements
|
||||
for (; i < 10; i++) {
|
||||
mockScope.rows.push({row: i});
|
||||
}
|
||||
mockTelemetryHandler.handle.mostRecentCall.args[1]();
|
||||
expect(mockScope.rows.length).toBe(controller.maxRows);
|
||||
expect(mockScope.rows[mockScope.rows.length-1]).toBe(mockTableRow);
|
||||
expect(mockScope.rows[0].row).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('enables autoscroll for event telemetry', function () {
|
||||
@@ -47,18 +47,36 @@ define(
|
||||
'listen'
|
||||
]);
|
||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
||||
'getCapability'
|
||||
'getCapability',
|
||||
'getModel'
|
||||
]);
|
||||
mockDomainObject.getCapability.andReturn(mockCapability);
|
||||
mockDomainObject.getModel.andReturn({});
|
||||
|
||||
mockScope = jasmine.createSpyObj('scope', [
|
||||
'$watchCollection'
|
||||
'$watchCollection',
|
||||
'$watch',
|
||||
'$on'
|
||||
]);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
controller = new TableOptionsController(mockScope);
|
||||
});
|
||||
|
||||
it('Listens for changing domain object', function() {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('On destruction of controller, destroys listeners', function() {
|
||||
var unlistenFunc = jasmine.createSpy("unlisten");
|
||||
controller.listeners.push(unlistenFunc);
|
||||
expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
expect(unlistenFunc).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Registers a listener for mutation events on the object', function() {
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
expect(mockCapability.listen).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -139,8 +139,9 @@ define([
|
||||
{
|
||||
"key": "timeline",
|
||||
"name": "Timeline",
|
||||
"glyph": "S",
|
||||
"description": "A container for arranging Timelines and Activities in time.",
|
||||
"glyph": "\u0053",
|
||||
"description": "A time-oriented container that lets you enclose and organize other Timelines and Activities. The Timeline view provides both tabular and Gantt views as well as resource utilization graphing of Activities.",
|
||||
"priority": 502,
|
||||
"features": [
|
||||
"creation"
|
||||
],
|
||||
@@ -172,20 +173,24 @@ define([
|
||||
}
|
||||
],
|
||||
"model": {
|
||||
"composition": []
|
||||
"composition": [],
|
||||
"start": {
|
||||
"timestamp": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "activity",
|
||||
"name": "Activity",
|
||||
"glyph": "a",
|
||||
"glyph": "\u0061",
|
||||
"features": [
|
||||
"creation"
|
||||
],
|
||||
"contains": [
|
||||
"activity"
|
||||
],
|
||||
"description": "An action that takes place in time. You can define a start time and duration. Activities can be nested within other Activities, or within Timelines.",
|
||||
"description": "An event or process that starts and ends at a discrete datetime. Activities can be nested in other Activities, and can be added to Timelines. Activity Modes can be added to an Activity to define its resource utilization over time.",
|
||||
"priority": 501,
|
||||
"properties": [
|
||||
{
|
||||
"name": "Start date/time",
|
||||
@@ -211,17 +216,24 @@ define([
|
||||
"composition": [],
|
||||
"relationships": {
|
||||
"modes": []
|
||||
},
|
||||
"start": {
|
||||
"timestamp": 0
|
||||
},
|
||||
"duration": {
|
||||
"timestamp": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "mode",
|
||||
"name": "Activity Mode",
|
||||
"glyph": "A",
|
||||
"glyph": "\u0041",
|
||||
"features": [
|
||||
"creation"
|
||||
],
|
||||
"description": "Define resource utilizations over time, then apply to an Activity.",
|
||||
"description": "When a sub-system utilizes Power or Communications resources over time, you can define those values in an Activity Mode. Activity Modes can then be linked to Activities to allow resource utilization graphing and estimating in a Timeline.",
|
||||
"priority": 500,
|
||||
"model": {
|
||||
"resources": {
|
||||
"comms": 0,
|
||||
@@ -256,7 +268,7 @@ define([
|
||||
{
|
||||
"key": "values",
|
||||
"name": "Values",
|
||||
"glyph": "A",
|
||||
"glyph": "\u0041",
|
||||
"template": valuesTemplate,
|
||||
"type": "mode",
|
||||
"uses": [
|
||||
@@ -267,9 +279,9 @@ define([
|
||||
{
|
||||
"key": "timeline",
|
||||
"name": "Timeline",
|
||||
"glyph": "S",
|
||||
"glyph": "\u0053",
|
||||
"type": "timeline",
|
||||
"description": "A timeline view of Timelines and Activities.",
|
||||
"description": "A time-oriented container that lets you enclose and organize other Timelines and Activities. The Timeline view provides both tabular and Gantt views as well as resource utilization graphing of Activities.",
|
||||
"template": timelineTemplate,
|
||||
"editable": true,
|
||||
"toolbar": {
|
||||
@@ -278,18 +290,18 @@ define([
|
||||
"items": [
|
||||
{
|
||||
"method": "add",
|
||||
"glyph": "+",
|
||||
"glyph": "\u002b",
|
||||
"control": "menu-button",
|
||||
"text": "Add",
|
||||
"options": [
|
||||
{
|
||||
"name": "Timeline",
|
||||
"glyph": "S",
|
||||
"glyph": "\u0053",
|
||||
"key": "timeline"
|
||||
},
|
||||
{
|
||||
"name": "Activity",
|
||||
"glyph": "a",
|
||||
"glyph": "\u0061",
|
||||
"key": "activity"
|
||||
}
|
||||
]
|
||||
@@ -299,37 +311,39 @@ define([
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"glyph": "é",
|
||||
"description": "Graph resource utilization",
|
||||
"glyph": "\u00e9",
|
||||
"description": "Graph Resource Utilization",
|
||||
"control": "button",
|
||||
"method": "toggleGraph"
|
||||
},
|
||||
{
|
||||
"glyph": "A",
|
||||
"glyph": "\u0041",
|
||||
"control": "dialog-button",
|
||||
"description": "Apply Activity Modes...",
|
||||
"title": "Apply Activity Modes",
|
||||
"dialog": {
|
||||
"control": "selector",
|
||||
"name": "Modes",
|
||||
"type": "mode"
|
||||
"type": "mode",
|
||||
"layout": "controls-under"
|
||||
},
|
||||
"property": "modes"
|
||||
},
|
||||
{
|
||||
"glyph": "è",
|
||||
"glyph": "\u00e8",
|
||||
"description": "Edit Activity Link",
|
||||
"title": "Activity Link",
|
||||
"control": "dialog-button",
|
||||
"dialog": {
|
||||
"control": "textfield",
|
||||
"name": "Link",
|
||||
"pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$"
|
||||
"pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$",
|
||||
"cssclass": "l-input-lg"
|
||||
},
|
||||
"property": "link"
|
||||
},
|
||||
{
|
||||
"glyph": "G",
|
||||
"glyph": "\u0047",
|
||||
"description": "Edit Properties...",
|
||||
"control": "button",
|
||||
"method": "properties"
|
||||
@@ -340,9 +354,9 @@ define([
|
||||
"items": [
|
||||
{
|
||||
"method": "remove",
|
||||
"description": "Remove item",
|
||||
"description": "Remove Item",
|
||||
"control": "button",
|
||||
"glyph": "Z"
|
||||
"glyph": "\u005a"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
.handle {
|
||||
cursor: col-resize;
|
||||
&.mid {
|
||||
cursor: ew-resize;
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,17 +140,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-mode .s-swimlane,
|
||||
.s-status-editing .s-swimlane {
|
||||
cursor: pointer;
|
||||
.t-object-label {
|
||||
border-radius: $controlCr;
|
||||
cursor: move;
|
||||
padding: 2px 5px;
|
||||
&:hover {
|
||||
background: rgba($colorBodyFg, 0.3);
|
||||
color: pullForward($colorBodyFg, 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
.l-timeline-holder {
|
||||
@include absPosDefault();
|
||||
|
||||
.l-header {
|
||||
@include user-select(none);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.l-timeline-pane {
|
||||
@include absPosDefault();
|
||||
|
||||
@@ -33,6 +38,9 @@
|
||||
.l-swimlanes-holder {
|
||||
@include absPosDefault();
|
||||
top: $timelineTopPaneHeaderH + 1;
|
||||
.l-col.l-plot-resource {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// Overall layout
|
||||
@@ -250,21 +258,15 @@
|
||||
|
||||
&.l-plot-resource {
|
||||
border-left: none !important;
|
||||
cursor: pointer;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&.l-title {
|
||||
width: $timelineColTitleW;
|
||||
.rep-object-label[draggable="true"] {
|
||||
.rep-object-label {
|
||||
border-radius: $basicCr;
|
||||
@include transition(background-color, 0.25s);
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 $interiorMarginSm;
|
||||
&:hover {
|
||||
background-color: $colorItemTreeHoverBg;
|
||||
}
|
||||
padding: 0 $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,3 +316,11 @@
|
||||
height: 5px
|
||||
}
|
||||
}
|
||||
|
||||
.s-status-editing .l-title .rep-object-label[draggable="true"] {
|
||||
@include transition(background-color, 0.25s);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: $colorItemTreeHoverBg;
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,11 @@
|
||||
}">
|
||||
<div class="l-cols">
|
||||
<span class="l-col l-col-icon l-plot-resource"
|
||||
ng-click="ngModel.toggleGraph()">
|
||||
ng-click="ngModel.toggleGraph()"
|
||||
title="Click to enable or disable inclusion in Resource Graphing">
|
||||
<span class="ui-symbol"
|
||||
ng-show="ngModel.graph()">
|
||||
ng-show="ngModel.graph()"
|
||||
>
|
||||
é
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -26,20 +26,17 @@
|
||||
<!-- LEFT PANE: TABULAR AND RESOURCE LEGEND AREAS -->
|
||||
<mct-split-pane anchor="bottom"
|
||||
position="pane.y"
|
||||
class="abs horizontal split-pane-component l-timeline-pane l-pane-l t-pane-v"
|
||||
>
|
||||
class="abs horizontal split-pane-component l-timeline-pane l-pane-l t-pane-v">
|
||||
<!-- TOP PANE TABULAR AREA. ADD CLASS "hidden" FOR INTERIM NO-TABULAR DELIVERY -->
|
||||
<div class="split-pane-component s-timeline-tabular l-timeline-pane t-pane-h l-pane-top">
|
||||
<!-- TABULAR LEFT FIXED AREA -->
|
||||
<div
|
||||
class="t-pane-v l-pane-l l-tabular-l"
|
||||
ng-if="true"
|
||||
>
|
||||
<div class="t-pane-v l-pane-l l-tabular-l"
|
||||
ng-if="true">
|
||||
|
||||
<div class="t-header l-header s-header">
|
||||
<div class="l-cols">
|
||||
<span class="l-col l-col-icon l-plot-resource ui-symbol">é</span>
|
||||
<span class="l-col l-col-icon l-col-link ui-symbol">è</span>
|
||||
<span title="Resource Graphing: click a row to toggle" class="l-col l-col-icon l-plot-resource ui-symbol">é</span>
|
||||
<span title="Activity Links" class="l-col l-col-icon l-col-link ui-symbol">è</span>
|
||||
<span class="l-col l-title">Title</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,9 +51,7 @@
|
||||
</div>
|
||||
|
||||
<!-- TABULAR RIGHT HORZ SCROLLING AREA -->
|
||||
<div
|
||||
class="t-pane-v l-pane-r l-tabular-r"
|
||||
>
|
||||
<div class="t-pane-v l-pane-r l-tabular-r">
|
||||
<div class="l-width">
|
||||
<div class="t-header l-header s-header">
|
||||
<div class="l-cols">
|
||||
@@ -102,12 +97,10 @@
|
||||
<span ng-controller="TimelineZoomController as zoomController" class="abs">
|
||||
<mct-split-pane anchor="bottom"
|
||||
position="pane.y"
|
||||
class="abs split-pane-component l-timeline-pane l-pane-r t-pane-v"
|
||||
>
|
||||
class="abs split-pane-component l-timeline-pane l-pane-r t-pane-v">
|
||||
|
||||
<!-- TOP PANE GANTT BARS -->
|
||||
<div class="split-pane-component l-timeline-pane t-pane-h l-pane-top t-timeline-gantt l-timeline-gantt s-timeline-gantt"
|
||||
>
|
||||
<div class="split-pane-component l-timeline-pane t-pane-h l-pane-top t-timeline-gantt l-timeline-gantt s-timeline-gantt">
|
||||
<div class="l-hover-btns-holder s-hover-btns-holder t-btns-zoom">
|
||||
<a class="t-btn l-btn s-btn"
|
||||
ng-click="zoomController.zoom(-1)"
|
||||
@@ -124,8 +117,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div style="overflow: hidden; position: absolute; left: 0; top: 0; right: 0; height: 30px;"
|
||||
mct-scroll-x="scroll.x">
|
||||
<div style="overflow: hidden; position: absolute; left: 0; top: 0; right: 0; height: 30px;" mct-scroll-x="scroll.x">
|
||||
<mct-include key="'timeline-ticks'"
|
||||
parameters="{
|
||||
fullWidth: zoomController.toPixels(zoomController.duration()),
|
||||
@@ -137,9 +129,6 @@
|
||||
</mct-include>
|
||||
</div>
|
||||
|
||||
<!-- TO-DO:
|
||||
Make this control y-scroll of both .t-swimlanes-holder elements in TOP PANE TABULAR AREA
|
||||
-->
|
||||
<div class="t-swimlanes-holder l-swimlanes-holder"
|
||||
mct-scroll-x="scroll.x"
|
||||
mct-scroll-y="scroll.y">
|
||||
@@ -175,12 +164,9 @@
|
||||
mct-drag-up="handle.finish()">
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- HORZ SPLITTER -->
|
||||
@@ -188,11 +174,8 @@
|
||||
|
||||
<!-- BOTTOM PANE RESOURCE GRAPHS AND RIGHT PANE HORIZONTAL SCROLL CONTROL -->
|
||||
<div class="split-pane-component l-timeline-resource-graph l-timeline-pane t-pane-h l-pane-btm">
|
||||
|
||||
<div class="l-graphs-holder"
|
||||
mct-resize="scroll.width = bounds.width">
|
||||
|
||||
<!-- TO-DO: Make this control y-scroll of .t-graph-labels-holder -->
|
||||
<div class="t-graphs l-graphs">
|
||||
<mct-include key="'timeline-resource-graphs'"
|
||||
parameters="{
|
||||
@@ -202,9 +185,7 @@
|
||||
}">
|
||||
</mct-include>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- TO-DO: Make this control x-scroll of .t-timeline-gantt -->
|
||||
<div mct-scroll-x="scroll.x"
|
||||
class="t-pane-r-scroll-h-control l-scroll-control s-scroll-control">
|
||||
<div class="l-width-control"
|
||||
|
||||
@@ -45,11 +45,13 @@ define(
|
||||
function modes(value) {
|
||||
// Can be used as a setter...
|
||||
if (arguments.length > 0 && Array.isArray(value)) {
|
||||
// Update the relationships
|
||||
mutator.mutate(function (model) {
|
||||
model.relationships = model.relationships || {};
|
||||
model.relationships[ACTIVITY_RELATIONSHIP] = value;
|
||||
}).then(persister.persist);
|
||||
if ((model.relationships || {})[ACTIVITY_RELATIONSHIP] !== value) {
|
||||
// Update the relationships
|
||||
mutator.mutate(function (model) {
|
||||
model.relationships = model.relationships || {};
|
||||
model.relationships[ACTIVITY_RELATIONSHIP] = value;
|
||||
}).then(persister.persist);
|
||||
}
|
||||
}
|
||||
// ...otherwise, use as a getter
|
||||
return (model.relationships || {})[ACTIVITY_RELATIONSHIP] || [];
|
||||
|
||||
@@ -74,12 +74,6 @@ define(
|
||||
return swimlane.children.map(matches).reduce(or, false);
|
||||
}
|
||||
|
||||
// Remove a domain object from its current location
|
||||
function remove(domainObject) {
|
||||
return domainObject &&
|
||||
domainObject.getCapability('action').perform('remove');
|
||||
}
|
||||
|
||||
// Initiate mutation of a domain object
|
||||
function doMutate(domainObject, mutator) {
|
||||
return asPromise(
|
||||
@@ -106,6 +100,27 @@ define(
|
||||
return swimlane.highlight() || expandedForDropInto();
|
||||
}
|
||||
|
||||
function isReorder(targetObject, droppedObject) {
|
||||
var droppedContext = droppedObject.getCapability('context'),
|
||||
droppedParent =
|
||||
droppedContext && droppedContext.getParent(),
|
||||
droppedParentId = droppedParent && droppedParent.getId();
|
||||
return targetObject.getId() === droppedParentId;
|
||||
}
|
||||
|
||||
// Choose an appropriate composition action for the drag-and-drop
|
||||
function chooseAction(targetObject, droppedObject) {
|
||||
var actionCapability =
|
||||
targetObject.getCapability('action'),
|
||||
actionKey = droppedObject.hasCapability('editor') ?
|
||||
'move' : 'link';
|
||||
|
||||
return actionCapability && actionCapability.getActions({
|
||||
key: actionKey,
|
||||
selectedObject: droppedObject
|
||||
})[0];
|
||||
}
|
||||
|
||||
// Choose an index for insertion in a domain object's composition
|
||||
function chooseTargetIndex(id, offset, composition) {
|
||||
return Math.max(
|
||||
@@ -121,6 +136,10 @@ define(
|
||||
function insert(id, target, indexOffset) {
|
||||
var myId = swimlane.domainObject.getId();
|
||||
return doMutate(target, function (model) {
|
||||
model.composition =
|
||||
model.composition.filter(function (compId) {
|
||||
return compId !== id;
|
||||
});
|
||||
model.composition.splice(
|
||||
chooseTargetIndex(myId, indexOffset, model.composition),
|
||||
0,
|
||||
@@ -129,17 +148,22 @@ define(
|
||||
});
|
||||
}
|
||||
|
||||
// Check if a compose action is allowed for the object in this
|
||||
// swimlane (we handle the link differently to set the index,
|
||||
// but check for the existence of the action to invole the
|
||||
// relevant policies.)
|
||||
function allowsCompose(swimlane, domainObject) {
|
||||
var actionCapability =
|
||||
swimlane.domainObject.getCapability('action');
|
||||
return actionCapability && actionCapability.getActions({
|
||||
key: 'compose',
|
||||
selectedObject: domainObject
|
||||
}).length > 0;
|
||||
function canDrop(targetObject, droppedObject) {
|
||||
|
||||
return isReorder(targetObject, droppedObject) ||
|
||||
!!chooseAction(targetObject, droppedObject);
|
||||
}
|
||||
|
||||
function drop(domainObject, targetObject, indexOffset) {
|
||||
function changeIndex() {
|
||||
var id = domainObject.getId();
|
||||
return insert(id, targetObject, indexOffset);
|
||||
}
|
||||
|
||||
return isReorder(targetObject, domainObject) ?
|
||||
changeIndex() :
|
||||
chooseAction(targetObject, domainObject)
|
||||
.perform().then(changeIndex);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -154,7 +178,7 @@ define(
|
||||
return inEditMode() &&
|
||||
!pathContains(swimlane, id) &&
|
||||
!contains(swimlane, id) &&
|
||||
allowsCompose(swimlane, domainObject);
|
||||
canDrop(swimlane.domainObject, domainObject);
|
||||
},
|
||||
/**
|
||||
* Check if a drop-after should be allowed for this swimlane,
|
||||
@@ -169,7 +193,7 @@ define(
|
||||
return inEditMode() &&
|
||||
target &&
|
||||
!pathContains(target, id) &&
|
||||
allowsCompose(target, domainObject);
|
||||
canDrop(target.domainObject, domainObject);
|
||||
},
|
||||
/**
|
||||
* Drop the provided domain object into a timeline. This is
|
||||
@@ -192,11 +216,7 @@ define(
|
||||
Number.POSITIVE_INFINITY;
|
||||
|
||||
if (swimlane.highlight() || swimlane.highlightBottom()) {
|
||||
// Remove the domain object from its original location...
|
||||
return asPromise(remove(domainObject)).then(function () {
|
||||
// ...then insert it at its new location.
|
||||
insert(id, dropTarget, dropIndexOffset);
|
||||
});
|
||||
return drop(domainObject, dropTarget, dropIndexOffset);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -204,4 +224,4 @@ define(
|
||||
|
||||
return TimelineSwimlaneDropHandler;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -82,12 +82,18 @@ define(
|
||||
),
|
||||
draggedSwimlane = dndService.getData(
|
||||
SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE
|
||||
);
|
||||
),
|
||||
droppedObject = draggedSwimlane ?
|
||||
draggedSwimlane.domainObject :
|
||||
dndService.getData(
|
||||
SwimlaneDragConstants.MCT_EXTENDED_DRAG_TYPE
|
||||
);
|
||||
|
||||
if (id) {
|
||||
event.stopPropagation();
|
||||
e.preventDefault();
|
||||
// Delegate the drop to the swimlane itself
|
||||
swimlane.drop(id, (draggedSwimlane || {}).domainObject);
|
||||
swimlane.drop(id, droppedObject);
|
||||
}
|
||||
|
||||
// Clear the swimlane highlights
|
||||
@@ -97,21 +103,37 @@ define(
|
||||
|
||||
function link(scope, element, attrs) {
|
||||
// Lookup swimlane by evaluating this attribute
|
||||
function swimlane() {
|
||||
function lookupSwimlane() {
|
||||
return scope.$eval(attrs.mctSwimlaneDrop);
|
||||
}
|
||||
// Handle dragover
|
||||
element.on('dragover', function (e) {
|
||||
dragOver(e, element, swimlane());
|
||||
var swimlane = lookupSwimlane(),
|
||||
highlight = swimlane.highlight(),
|
||||
highlightBottom = swimlane.highlightBottom();
|
||||
|
||||
dragOver(e, element, swimlane);
|
||||
|
||||
if (highlightBottom !== swimlane.highlightBottom() ||
|
||||
highlight !== swimlane.highlight()) {
|
||||
scope.$apply();
|
||||
}
|
||||
});
|
||||
// Handle drops
|
||||
element.on('drop', function (e) {
|
||||
drop(e, element, swimlane());
|
||||
drop(e, element, lookupSwimlane());
|
||||
scope.$apply();
|
||||
});
|
||||
// Clear highlights when drag leaves this swimlane
|
||||
element.on('dragleave', function () {
|
||||
swimlane().highlight(false);
|
||||
swimlane().highlightBottom(false);
|
||||
var swimlane = lookupSwimlane(),
|
||||
wasHighlighted = swimlane.highlight() ||
|
||||
swimlane.highlightBottom();
|
||||
swimlane.highlight(false);
|
||||
swimlane.highlightBottom(false);
|
||||
if (wasHighlighted) {
|
||||
scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,14 @@ define(
|
||||
mockCapabilities,
|
||||
testModel,
|
||||
mockPromise,
|
||||
testModes,
|
||||
decorator;
|
||||
|
||||
beforeEach(function () {
|
||||
mockSwimlane = {};
|
||||
mockCapabilities = {};
|
||||
testModel = {};
|
||||
testModes = ['a', 'b', 'c'];
|
||||
|
||||
mockSelection = jasmine.createSpyObj('selection', ['select', 'get']);
|
||||
|
||||
@@ -135,6 +137,22 @@ define(
|
||||
expect(mockCapabilities.persistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not mutate modes when unchanged", function () {
|
||||
testModel.relationships = { modes: testModes };
|
||||
decorator.modes(testModes);
|
||||
expect(mockCapabilities.mutation.mutate).not.toHaveBeenCalled();
|
||||
expect(testModel.relationships.modes).toEqual(testModes);
|
||||
});
|
||||
|
||||
it("does mutate modes when changed", function () {
|
||||
var testModes2 = ['d', 'e', 'f'];
|
||||
testModel.relationships = { modes: testModes };
|
||||
decorator.modes(testModes2);
|
||||
expect(mockCapabilities.mutation.mutate).toHaveBeenCalled();
|
||||
mockCapabilities.mutation.mutate.mostRecentCall.args[0](testModel);
|
||||
expect(testModel.relationships.modes).toBe(testModes2);
|
||||
});
|
||||
|
||||
it("does not provide a 'remove' method with no parent", function () {
|
||||
expect(decorator.remove).not.toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
@@ -31,9 +31,13 @@ define(
|
||||
mockOtherObject,
|
||||
mockActionCapability,
|
||||
mockPersistence,
|
||||
mockContext,
|
||||
mockAction,
|
||||
handler;
|
||||
|
||||
beforeEach(function () {
|
||||
var mockPromise = jasmine.createSpyObj('promise', ['then']);
|
||||
|
||||
mockSwimlane = jasmine.createSpyObj(
|
||||
"swimlane",
|
||||
[ "highlight", "highlightBottom" ]
|
||||
@@ -60,6 +64,11 @@ define(
|
||||
[ "getId", "getCapability", "useCapability", "hasCapability" ]
|
||||
);
|
||||
|
||||
mockAction = jasmine.createSpyObj('action', ['perform']);
|
||||
mockAction.perform.andReturn(mockPromise);
|
||||
mockPromise.then.andCallFake(function (callback) {
|
||||
callback();
|
||||
});
|
||||
|
||||
mockOtherObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
@@ -67,20 +76,34 @@ define(
|
||||
);
|
||||
mockActionCapability = jasmine.createSpyObj("action", ["perform", "getActions"]);
|
||||
mockPersistence = jasmine.createSpyObj("persistence", ["persist"]);
|
||||
mockContext = jasmine.createSpyObj('context', [ 'getParent' ]);
|
||||
|
||||
mockActionCapability.getActions.andReturn([{}]);
|
||||
mockActionCapability.getActions.andReturn([mockAction]);
|
||||
mockSwimlane.parent.domainObject.getId.andReturn('a');
|
||||
mockSwimlane.domainObject.getId.andReturn('b');
|
||||
mockSwimlane.children[0].domainObject.getId.andReturn('c');
|
||||
mockOtherObject.getId.andReturn('d');
|
||||
|
||||
|
||||
mockSwimlane.domainObject.getCapability.andCallFake(function (c) {
|
||||
return {
|
||||
action: mockActionCapability,
|
||||
persistence: mockPersistence
|
||||
}[c];
|
||||
});
|
||||
mockOtherObject.getCapability.andReturn(mockActionCapability);
|
||||
mockSwimlane.parent.domainObject.getCapability.andCallFake(function (c) {
|
||||
return {
|
||||
action: mockActionCapability,
|
||||
persistence: mockPersistence
|
||||
}[c];
|
||||
});
|
||||
mockOtherObject.getCapability.andCallFake(function (c) {
|
||||
return {
|
||||
action: mockActionCapability,
|
||||
context: mockContext
|
||||
}[c];
|
||||
});
|
||||
mockContext.getParent.andReturn(mockOtherObject);
|
||||
|
||||
mockSwimlane.domainObject.hasCapability.andReturn(true);
|
||||
|
||||
@@ -89,13 +112,17 @@ define(
|
||||
|
||||
it("disallows drop outside of edit mode", function () {
|
||||
// Verify precondition
|
||||
expect(handler.allowDropIn('d')).toBeTruthy();
|
||||
expect(handler.allowDropAfter('d')).toBeTruthy();
|
||||
expect(handler.allowDropIn('d', mockSwimlane.domainObject))
|
||||
.toBeTruthy();
|
||||
expect(handler.allowDropAfter('d', mockSwimlane.domainObject))
|
||||
.toBeTruthy();
|
||||
// Act as if we're not in edit mode
|
||||
mockSwimlane.domainObject.hasCapability.andReturn(false);
|
||||
// Now, they should be disallowed
|
||||
expect(handler.allowDropIn('d')).toBeFalsy();
|
||||
expect(handler.allowDropAfter('d')).toBeFalsy();
|
||||
expect(handler.allowDropIn('d', mockSwimlane.domainObject))
|
||||
.toBeFalsy();
|
||||
expect(handler.allowDropAfter('d', mockSwimlane.domainObject))
|
||||
.toBeFalsy();
|
||||
|
||||
// Verify that editor capability was really checked for
|
||||
expect(mockSwimlane.domainObject.hasCapability)
|
||||
@@ -103,8 +130,9 @@ define(
|
||||
});
|
||||
|
||||
it("disallows dropping of parents", function () {
|
||||
expect(handler.allowDropIn('a')).toBeFalsy();
|
||||
expect(handler.allowDropAfter('a')).toBeFalsy();
|
||||
var mockParent = mockSwimlane.parent.domainObject;
|
||||
expect(handler.allowDropIn('a', mockParent)).toBeFalsy();
|
||||
expect(handler.allowDropAfter('a', mockParent)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("does not drop when no highlight state is present", function () {
|
||||
@@ -121,7 +149,7 @@ define(
|
||||
it("inserts into when highlighted", function () {
|
||||
var testModel = { composition: [ 'c' ] };
|
||||
mockSwimlane.highlight.andReturn(true);
|
||||
handler.drop('d');
|
||||
handler.drop('d', mockOtherObject);
|
||||
// Should have mutated
|
||||
expect(mockSwimlane.domainObject.useCapability)
|
||||
.toHaveBeenCalledWith("mutation", jasmine.any(Function));
|
||||
@@ -133,24 +161,11 @@ define(
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes objects before insertion, if provided", function () {
|
||||
var testModel = { composition: [ 'c' ] };
|
||||
mockSwimlane.highlight.andReturn(true);
|
||||
handler.drop('d', mockOtherObject);
|
||||
// Should have invoked a remove action
|
||||
expect(mockActionCapability.perform)
|
||||
.toHaveBeenCalledWith('remove');
|
||||
// Verify that mutator still ran as expected
|
||||
mockSwimlane.domainObject.useCapability.mostRecentCall
|
||||
.args[1](testModel);
|
||||
expect(testModel.composition).toEqual(['c', 'd']);
|
||||
});
|
||||
|
||||
it("inserts after as a peer when highlighted at the bottom", function () {
|
||||
var testModel = { composition: [ 'x', 'b', 'y' ] };
|
||||
mockSwimlane.highlightBottom.andReturn(true);
|
||||
mockSwimlane.expanded = false;
|
||||
handler.drop('d');
|
||||
handler.drop('d', mockOtherObject);
|
||||
// Should have mutated
|
||||
expect(mockSwimlane.parent.domainObject.useCapability)
|
||||
.toHaveBeenCalledWith("mutation", jasmine.any(Function));
|
||||
@@ -164,7 +179,7 @@ define(
|
||||
var testModel = { composition: [ 'c' ] };
|
||||
mockSwimlane.highlightBottom.andReturn(true);
|
||||
mockSwimlane.expanded = true;
|
||||
handler.drop('d');
|
||||
handler.drop('d', mockOtherObject);
|
||||
// Should have mutated
|
||||
expect(mockSwimlane.domainObject.useCapability)
|
||||
.toHaveBeenCalledWith("mutation", jasmine.any(Function));
|
||||
@@ -179,7 +194,7 @@ define(
|
||||
mockSwimlane.highlightBottom.andReturn(true);
|
||||
mockSwimlane.expanded = true;
|
||||
mockSwimlane.children = [];
|
||||
handler.drop('d');
|
||||
handler.drop('d', mockOtherObject);
|
||||
// Should have mutated
|
||||
expect(mockSwimlane.parent.domainObject.useCapability)
|
||||
.toHaveBeenCalledWith("mutation", jasmine.any(Function));
|
||||
@@ -189,6 +204,38 @@ define(
|
||||
expect(testModel.composition).toEqual([ 'x', 'b', 'd', 'y']);
|
||||
});
|
||||
|
||||
it("allows reordering within a parent", function () {
|
||||
var testModel = { composition: [ 'x', 'b', 'y', 'd' ] };
|
||||
|
||||
mockSwimlane.highlightBottom.andReturn(true);
|
||||
mockSwimlane.expanded = true;
|
||||
mockSwimlane.children = [];
|
||||
mockContext.getParent
|
||||
.andReturn(mockSwimlane.parent.domainObject);
|
||||
handler.drop('d', mockOtherObject);
|
||||
|
||||
waitsFor(function () {
|
||||
return mockSwimlane.parent.domainObject.useCapability
|
||||
.calls.length > 0;
|
||||
});
|
||||
|
||||
runs(function () {
|
||||
mockSwimlane.parent.domainObject.useCapability.mostRecentCall
|
||||
.args[1](testModel);
|
||||
expect(testModel.composition).toEqual([ 'x', 'b', 'd', 'y']);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not invoke an action when reordering", function () {
|
||||
mockSwimlane.highlightBottom.andReturn(true);
|
||||
mockSwimlane.expanded = true;
|
||||
mockSwimlane.children = [];
|
||||
mockContext.getParent
|
||||
.andReturn(mockSwimlane.parent.domainObject);
|
||||
handler.drop('d', mockOtherObject);
|
||||
expect(mockAction.perform).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -55,7 +55,7 @@ define(
|
||||
'dndService',
|
||||
['setData', 'getData', 'removeData']
|
||||
);
|
||||
mockScope = jasmine.createSpyObj('$scope', ['$eval']);
|
||||
mockScope = jasmine.createSpyObj('$scope', ['$eval', '$apply']);
|
||||
mockElement = jasmine.createSpyObj('element', ['on']);
|
||||
testAttrs = { mctSwimlaneDrop: "mockSwimlane" };
|
||||
mockSwimlane = jasmine.createSpyObj(
|
||||
@@ -118,6 +118,7 @@ define(
|
||||
|
||||
expect(mockSwimlane.highlight).toHaveBeenCalledWith(true);
|
||||
expect(mockSwimlane.highlightBottom).toHaveBeenCalledWith(false);
|
||||
expect(mockScope.$apply).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates bottom highlights on drag over", function () {
|
||||
@@ -128,6 +129,7 @@ define(
|
||||
|
||||
expect(mockSwimlane.highlight).toHaveBeenCalledWith(false);
|
||||
expect(mockSwimlane.highlightBottom).toHaveBeenCalledWith(true);
|
||||
expect(mockScope.$apply).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("respects swimlane's allowDropIn response", function () {
|
||||
@@ -157,12 +159,20 @@ define(
|
||||
it("notifies swimlane on drop", function () {
|
||||
handlers.drop(testEvent);
|
||||
expect(mockSwimlane.drop).toHaveBeenCalledWith('abc', 'someDomainObject');
|
||||
expect(mockScope.$apply).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("invokes preventDefault on drop", function () {
|
||||
handlers.drop(testEvent);
|
||||
expect(testEvent.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears highlights when drag leaves", function () {
|
||||
mockSwimlane.highlight.andReturn(true);
|
||||
handlers.dragleave();
|
||||
expect(mockSwimlane.highlight).toHaveBeenCalledWith(false);
|
||||
expect(mockSwimlane.highlightBottom).toHaveBeenCalledWith(false);
|
||||
expect(mockScope.$apply).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,6 +27,14 @@
|
||||
<span class="l-click-area" ng-click="toggle.toggle()"></span>
|
||||
|
||||
<span class="ui-symbol icon">{{structure.glyph}}</span>
|
||||
<span ng-style="{
|
||||
background: ngModel[field],
|
||||
display: 'inline-block',
|
||||
height: '11px',
|
||||
width: '11px',
|
||||
}"
|
||||
ng-show="ngModel[field]">
|
||||
</span>
|
||||
<span class="title-label" ng-if="structure.text">
|
||||
{{structure.text}}
|
||||
</span>
|
||||
@@ -62,4 +70,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
invalid: mctFormInner.$dirty && !mctFormInner.$valid,
|
||||
first: $index < 1,
|
||||
'l-controls-first': row.layout === 'control-first',
|
||||
'l-input-sm': row.inputsize === 'sm'
|
||||
'l-controls-under': row.layout === 'controls-under'
|
||||
}">
|
||||
<div class='label flex-elem' title="{{row.description}}">
|
||||
{{row.name}}
|
||||
|
||||
@@ -123,6 +123,7 @@ define(
|
||||
// destination domain object's composition, and persist
|
||||
// the change.
|
||||
if (id) {
|
||||
e.preventDefault();
|
||||
$q.when(action && action.perform()).then(function (result) {
|
||||
//Don't go into edit mode for folders
|
||||
if (domainObjectType!=='folder') {
|
||||
|
||||
@@ -194,6 +194,11 @@ define(
|
||||
);
|
||||
});
|
||||
|
||||
it("invokes preventDefault on drop", function () {
|
||||
callbacks.drop(mockEvent);
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -26,10 +26,6 @@ define(
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
// Date format to use for domain values; in particular,
|
||||
// use day-of-year instead of month/day
|
||||
var VALUE_FORMAT_DIGITS = 3;
|
||||
|
||||
/**
|
||||
* The TelemetryFormatter is responsible for formatting (as text
|
||||
* for display) values along either the domain (usually time) or
|
||||
@@ -73,7 +69,7 @@ define(
|
||||
* value, suitable for display.
|
||||
*/
|
||||
TelemetryFormatter.prototype.formatRangeValue = function (v, key) {
|
||||
return isNaN(v) ? String(v) : v.toFixed(VALUE_FORMAT_DIGITS);
|
||||
return String(v);
|
||||
};
|
||||
|
||||
return TelemetryFormatter;
|
||||
|
||||
@@ -59,7 +59,10 @@ define(
|
||||
});
|
||||
|
||||
it("formats ranges as values", function () {
|
||||
expect(formatter.formatRangeValue(10)).toEqual("10.000");
|
||||
var value = 3.14159265352979323846264338, // not pi
|
||||
formatted = formatter.formatRangeValue(value);
|
||||
// Make sure we don't lose information by formatting
|
||||
expect(parseFloat(formatted)).toEqual(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user