Compare commits

..

105 Commits

Author SHA1 Message Date
Henry
f34e8ba61b Modified code to call resize on every row add. Removed optimization to only resize when needed, because in fact resuze is necessary on every update in order to set vertical scroll size 2016-04-11 13:09:02 -07:00
Henry
2fb9b65652 Made comment a little more accurate 2016-04-11 12:57:27 -07:00
Henry
0c6b4a5a23 [Tables] Fix to correct sorting in realtime tables 2016-04-11 12:57:27 -07:00
Henry
20672ad028 [Tables] #801 Documented MctTable directive 2016-04-11 12:55:56 -07:00
Henry
99ba9edb95 Merged
Resolved merge conflicts

Resolved merge conflicts
2016-04-11 12:55:53 -07:00
Henry
23a8c305c1 [Table] #798 Simplified markup, moved styles to external stylesheet 2016-04-11 12:23:36 -07:00
Pete Richards
c591ade479 Merge remote-tracking branch 'origin/781' 2016-04-06 10:35:40 -07:00
Victor Woeltjen
8f4f0cb78e Merge pull request #785 from nasa/open677_cleanup
[Timelines] #677 Removed temporary markup
2016-04-05 12:24:58 -07:00
Victor Woeltjen
e87280aa15 Merge pull request #821 from nasa/show-selected-color
[Control] Color input shows selected color
2016-04-05 12:20:14 -07:00
Pete Richards
e0a2d02d23 [Control] Color input shows selected color
Update color input to show selected color when expanded.
2016-04-05 11:57:45 -07:00
Victor Woeltjen
73012233b8 [Build] Bump version number
...to begin sprint Heinlein,
https://github.com/nasa/openmct/milestones/Heinlein
2016-04-04 09:33:58 -07:00
Victor Woeltjen
339916ccd4 [Build] Remove SNAPSHOT status
...to tag end of release Gibson,
https://github.com/nasa/openmct/milestones/Gibson
2016-04-04 09:32:21 -07:00
Victor Woeltjen
70acef6905 [Instantiation] Ensure new models have modified timestamp
...to avoid https://github.com/nasa/openmct/issues/745#issuecomment-204561163
2016-04-01 14:27:26 -07:00
Victor Woeltjen
da09ffd3fa [Persistence] Don't evict models on persist
https://github.com/nasa/openmct/issues/745#issuecomment-204559209
2016-04-01 13:38:13 -07:00
Pete Richards
5b98da6681 Merge pull request #807 from nasa/timeline-model-717
[Timeline] Add default values to model
2016-04-01 10:10:15 -07:00
Andrew Henry
2040abb768 Merge pull request #808 from nasa/revert-open-793
Revert "Merge remote-tracking branch 'origin/open793'"
2016-03-31 16:37:18 -07:00
Pete Richards
d35fccbbe8 Revert "Merge remote-tracking branch 'origin/open793'"
This reverts commit f49552779a, reversing
changes made to e068173f3e.
2016-03-31 16:30:35 -07:00
Victor Woeltjen
06c6832676 [Timeline] Add default values to model
Add default values to model, such that editing which occurs
before user supplies these properties does not cause errors
to occur. Directly addresses #717, indirectly addresses
remaining errant behavior associated with #790.
2016-03-31 15:46:56 -07:00
Pete Richards
f49552779a Merge remote-tracking branch 'origin/open793' 2016-03-31 14:14:08 -07:00
Henry
200a426f17 [Tables] Addressed style concerns from code review 2016-03-31 12:56:29 -07:00
Pete Richards
e068173f3e Merge remote-tracking branch 'origin/move-copy-policy-792' 2016-03-31 12:49:06 -07:00
Victor Woeltjen
4441e88769 Merge pull request #803 from nasa/drag-drop-688
[Firefox] Invoke preventDefault on drop
2016-03-31 11:02:26 -07:00
Victor Woeltjen
254a944d7a [Firefox] Invoke preventDefault on drop
...such that Firefox does not try to treat drop as a new URL
to navigate to. Addresses #688 and #527
2016-03-31 10:34:24 -07:00
Henry
0c00061cbc Added mutation listener 2016-03-31 10:26:34 -07:00
Andrew Henry
dee0613b81 Merge pull request #800 from nasa/timeline-reorder-789
[Timeline] Fix reordering behavior in timelines
2016-03-31 09:39:17 -07:00
Andrew Henry
6b6bada700 Merge pull request #788 from nasa/open445b
Review and integrate open445b
2016-03-31 09:36:03 -07:00
Victor Woeltjen
530b940a64 [Timeline] Fix reordering
Fix reordering behavior when dragging-and-dropping within timelines;
addresses #789.
2016-03-29 19:46:30 -07:00
Victor Woeltjen
e23bf5ed39 [Entanglement] Test CopyPolicy 2016-03-29 19:26:18 -07:00
Victor Woeltjen
6ecea9950d [Entanglement] Test MovePolicy 2016-03-29 19:22:22 -07:00
Victor Woeltjen
4816dddf41 [Entanglement] Wire in copy/move policies
#792
2016-03-29 13:14:08 -07:00
Victor Woeltjen
cae775f9bc [Entanglement] Add policies for Copy and Move
...to ensure that these are only performed on creatable and
mutable targets, respectively.
2016-03-29 13:10:35 -07:00
Henry
a4b79cdb5b [Tables] #793 Added fix for tables not appearing on refresh
Fixed failing tests
2016-03-28 17:48:01 -07:00
Henry
012a38cccd [Tables] Fix for table columns not being populated 2016-03-28 17:31:10 -07:00
Charles Hacskaylo
a01f7ddd2d [Frontend] Refinements to .loading and related
#445
Markup enhanced in wait-node.html;
More wait-spinner constants added;
Normalized appearance between tree-based
.loading and .s-status-pending;
Fixed .s-status-pending to work now that
mct-representation is gone from tree;
2016-03-24 15:10:22 -07:00
Charles Hacskaylo
e7e91e21fc [Frontend] Markup and CSS for loading tree items
#445
New wait-spinner constants;
Markup fixed in wait-node.html;
Styles for .loading when applied to
.tree-item.wait-node;
Changed from percent-of-parent-
width sizing of spinner to fixed size;
2016-03-24 15:10:02 -07:00
Pete Richards
cbea842c8b Merge branch 'timeline-highlight-767' 2016-03-24 13:33:56 -07:00
Pete Richards
cc5d14deec Merge branch '755a' 2016-03-24 13:21:59 -07:00
Pete Richards
4c5217d646 [Style] Standardize indentation 2016-03-24 13:20:49 -07:00
Pete Richards
5e54b193ca [Style] remove unnecessary parenthesis
See comment https://github.com/nasa/openmct/pull/782/files#r57380540
2016-03-24 13:14:53 -07:00
Pete Richards
cd7ff8ad85 Merge remote-tracking branch 'origin/timestamps-776' 2016-03-24 13:09:32 -07:00
Pete Richards
fa5d59bff8 Merge branch 'timeline-save-770b' 2016-03-24 13:06:53 -07:00
Pete Richards
ddbb72b88a [CacheService] Don't track ids twice
Track whether an object is in the cache based on whether it is
in the cache instead of utilizing a separate object for tracking
contents of cache.

See comment on https://github.com/nasa/openmct/pull/773/files
2016-03-24 12:57:53 -07:00
Charles Hacskaylo
88784d37fd [Frontend] Added priority
#787
2016-03-24 12:05:10 -07:00
Andrew Henry
700e605bbd Merge pull request #775 from nasa/timeline-drag-drop-679
[Timeline] Move (instead of link) when dragging and dropping within a timeline
2016-03-23 20:00:48 -07:00
Charles Hacskaylo
594f3b8ec2 [Frontend] Corrected wrong unicode value
#781
2016-03-23 19:30:20 -07:00
Charles Hacskaylo
b08d00ef3e [Frontend] Removed tab character
#781
2016-03-23 19:25:25 -07:00
Charles Hacskaylo
0643fb1f3f [Frontend] Updated icomoon project file
#781
2016-03-23 19:17:39 -07:00
Charles Hacskaylo
289debf19d [Frontend] Fixed hover problem with menu items
#781
#187
Noticed a problem with <a> tags in
Create menu items and fixed with display: block;
2016-03-23 19:10:59 -07:00
Charles Hacskaylo
7da1a4b2a3 [Frontend] Updated glyph defs and descriptions
#781
#187
Converted many glyph char defs to
their JSON unicode equivalents;
Updated descriptions for many domain
objects;
2016-03-23 19:04:32 -07:00
Charles Hacskaylo
528169de2c [Frontend] Updated glyphs
#781
Added multiple new and updated glyphs;
Updated glyph defs for Historic and
Real-Time Tables;
2016-03-23 17:42:58 -07:00
Victor Woeltjen
c13231b8e8 [Timeline] Remove NULL_ACTION, clarify logic
Per code review feedback,
https://github.com/nasa/openmct/pull/775/files#r57229759
2016-03-23 15:34:05 -07:00
Andrew Henry
3e4a3aeb9b Merge pull request #779 from nasa/precision-778b
[Data Formats] Format numeric values with full precision
2016-03-23 12:15:26 -07:00
Henry
650ef9bfa4 [Timelines] #677 Removed temporary markup 2016-03-23 11:27:40 -07:00
Victor Woeltjen
dd053f7e6e [Timeline] Invoke $apply from mct-swimlane-drop
...and add some checks to ensure this is necessary, to avoid
triggering a ton of digest cycles while dragging.
2016-03-22 17:16:41 -07:00
Victor Woeltjen
cb655d486b [Timeline] Test reordering within parent 2016-03-22 17:06:58 -07:00
Victor Woeltjen
3c52ceb71a [Timeline] Remove unused function
Removal of domain objects is handled by using the move action.
2016-03-22 17:01:45 -07:00
Victor Woeltjen
b2337dea97 [Timeline] Update failing specs
...and remove obsolete test case for handling drops in a timeline
2016-03-22 16:59:09 -07:00
Victor Woeltjen
dea6554e04 [Timeline] Allow drops from tree
Don't assume that all drops will be dropped swimlanes.
2016-03-22 12:26:01 -07:00
Victor Woeltjen
31d8c9a48f [Timeline] Allow linking during edit
...in cases where it is safe to do so (specifically, when the
linked-to object has already been persisted.)
2016-03-22 12:04:50 -07:00
Victor Woeltjen
f8682a7a29 Merge pull request #780 from nasa/open715
[Timelines] #715 Added a check to prevent mutation when modes are unchanged
2016-03-22 09:24:57 -07:00
Henry
e5544615cc [Timelines] #715 Added a check to prevent mutation when modes are unchanged 2016-03-21 17:01:38 -07:00
Victor Woeltjen
ec0cc572f6 [Add] Test model cache 2016-03-21 16:11:40 -07:00
Victor Woeltjen
6c2a28aba2 [Add] Add JSDoc to model cache 2016-03-21 16:05:05 -07:00
Victor Woeltjen
baccd005dc [Add] Update persistence capability spec
...to reflect removal of cached domain object models.
2016-03-21 15:08:27 -07:00
Victor Woeltjen
1e4ff5a73f [Add] Use cacheService from decorator spec 2016-03-21 15:05:28 -07:00
Victor Woeltjen
9f29382e18 [Add] Update spec for Instantiate
...to reflect usage of a model cache for #770
2016-03-21 15:04:07 -07:00
Victor Woeltjen
5f6b4adcda [Plot] Normalize number of digits
Use consistent number of digits for displayed plot values, to avoid
unreadable plot legends due to fix for #778
2016-03-21 14:45:00 -07:00
Victor Woeltjen
d6ab70447e [Data Format] Don't truncate data values 2016-03-21 14:35:58 -07:00
Victor Woeltjen
a411bac331 [Time Format] Append Z instead of zone offset
...for brevity (reduce visual noise)
2016-03-21 14:29:24 -07:00
Victor Woeltjen
5624c7d545 [Time Formats] Display UTC with milliseconds
#776
2016-03-21 13:04:59 -07:00
Victor Woeltjen
007741b4e7 [Timeline] Add move policy
...to restore suppression of Move for objects being edited
(relaxed for the specific case of moving one object being
edited into another object being edited, for use in Timelines.)
2016-03-21 12:07:19 -07:00
Victor Woeltjen
e0a69744e5 [Timeline] Deinline null action
...used for cases when no action is needed for timeline move
(and ids will just be reordered.)
2016-03-21 11:53:10 -07:00
Victor Woeltjen
e7a6c34bcc [Timeline] Handle drops for reordering
...as these would be disallowed by Move, normally.
2016-03-21 11:51:51 -07:00
Victor Woeltjen
7f3ac4077c [Timeline] Drop to correct targets 2016-03-21 11:43:02 -07:00
Victor Woeltjen
7eaffdc34a [Timeline] Don't suppress move during editing
...to allow this action to be used to support Timeline
drag-and-drop.
2016-03-21 11:42:33 -07:00
Victor Woeltjen
5034e88656 [Timeline] Use move action on drag-drop
...to avoid creating links when desired behavior is a move,
#679.
2016-03-21 11:18:18 -07:00
Charles Hacskaylo
aa48044345 [Frontend] Form styling
#772
Added ".l-controls-under" styling to allow
channel-selector layout;
Added new hint color constants and refined
style defs;
2016-03-21 11:13:43 -07:00
Victor Woeltjen
d12111d9b8 [Add] Fix promise chaining in AddAction 2016-03-21 10:32:02 -07:00
Victor Woeltjen
9c9db3c24f [Add] Remove obsolete variable reference
The cache has been externalized to allow writing to it
upon domain object instantiation.
2016-03-21 10:31:38 -07:00
Victor Woeltjen
3fe41575bd [Add] Add missing dependency
...to support caching of domain objects created during edit mode.
2016-03-21 10:31:09 -07:00
Victor Woeltjen
8fa030437e [Add] Remove edit awareness
Remove step where Added objects are persisted via the editor
capability; instead, persist via the usual persistence capability,
such that Edit mode may intervene (or not) as necessary.
As instantiated models are cached at least until persisted,
this workaround to allow newly-created models to be available
during editing is no longer necessary (and undesired consequences
such as #770 no longer occur)
2016-03-21 10:22:25 -07:00
Victor Woeltjen
17faf000b0 [Add] Cache models on instantiation
...to remove need for Edit to persist these immediately, which in
turn causes #770 and #678
2016-03-21 10:20:14 -07:00
Charles Hacskaylo
90c82f6ef2 [Frontend] Timeline tooltip and field input tweaks
#750
Refined tooltip for Resource Graphing;
Increased size of Activity Link input field;
2016-03-21 10:08:59 -07:00
Charles Hacskaylo
5e6fe16b93 [Frontend] Adding tooltips
#750
2016-03-17 16:21:04 -07:00
Charles Hacskaylo
f0ca6fdfdb [Frontend] Added tooltips
#750
Added tooltips to Timeline header elements;
2016-03-17 15:10:42 -07:00
Andrew Henry
ad4c456ca2 Merge pull request #763 from nasa/restrict-requirejs-version
[Package] Specify require minor version
2016-03-17 14:38:02 -07:00
Pete Richards
b0476edb8c [Package] Specify require minor version
Specify requirejs minor version of 2.1, as 2.2 is not compatible
with karma-requirejs.

Fixes build problems that appeared after latest requirejs version bump.
2016-03-17 14:28:22 -07:00
Pete Richards
8f6a287fb8 Merge remote-tracking branch 'origin/tree-refresh-745' 2016-03-17 11:40:53 -07:00
Victor Woeltjen
07d554d114 Merge pull request #759 from nasa/700a
Review and integrate 700a
2016-03-17 11:02:22 -07:00
Andrew Henry
24d2cecdcd Merge pull request #761 from nasa/open757
[Tables] #757 Added max row limit to streaming tables
2016-03-17 10:19:04 -07:00
Henry
8efa9c6aac [Tables] #757 Fixed code style issues 2016-03-17 10:09:59 -07:00
Henry
22e98274ca [Tables] #757 Added max row limit to streaming tables 2016-03-16 18:59:52 -07:00
Charles Hacskaylo
471b57f47c [Frontend] CSS tweaks
#750
Moved l-plot-resource cursor: pointer
to a more specific selector;
Added user-select: none to l-header;
Fixed s-status-editing styling so
that tabular labels only appear draggable
while editing;
2016-03-16 18:19:44 -07:00
Charles Hacskaylo
9c775c6715 [Frontend] Changed cursor property
#755
Changed s-timeline-gantt.mid to
use cursor: move instead of
ew-resize; Need to verify on Linux;
2016-03-16 17:36:07 -07:00
Charles Hacskaylo
1f09b7b0ac [Frontend] Fix for scrollbar flicker
#748
Was able to observe problem in Mac
Chrome. Moved body, html overflow: hidden
into startup-base.scss and change seems
to have fixed the problem;
2016-03-16 17:34:28 -07:00
Andrew Henry
b0cf9bbd29 Merge pull request #758 from nasa/error-message-753
[Tree] Don't assume context capability is present
2016-03-16 15:33:59 -07:00
Andrew Henry
4a1ca25e17 Merge pull request #754 from nasa/tree-status-styling-749
[Tree] Add status classes during editing
2016-03-16 15:26:40 -07:00
Charles Hacskaylo
cc97e567b6 [Frontend] Further style cleanups
#689
#740
Tweaked scrollbar thumb color;
Fixed color of icons in s-menu-btn
menus, particularly for Snow theme;
Removed commented code;
2016-03-16 15:17:08 -07:00
Charles Hacskaylo
53f03cddb7 [Frontend] Further cleanups to form elements
#689
Removed commented code;
2016-03-16 14:49:37 -07:00
Charles Hacskaylo
da1e6750a0 [Frontend] Removed reset.css and refs to modernizr
#700
Removed reset.css; Removed refs to
modernizr from licenses and readme.md;
2016-03-16 14:44:01 -07:00
Charles Hacskaylo
3258342783 [Frontend] Further cleanups to form elements
#700
#689
#740
Removed _selects.scss and moved classes
into _controls.scss;
Reorg'd _controls;
Moved classes into _elems.scss;
Added and fixed cssclass defs in multiple
bundle.js files to allow better field widths
in overlay dialogs;
Fixed overlay custom scrollbar colors;
Fixed alignment of required glyphs in forms;
2016-03-16 14:40:06 -07:00
Victor Woeltjen
73b7365ae2 [Tree] Don't assume context capability is present
Addresses #753 (newly-created objects may not have context,
causing errors when these are encountered by TreeNodeView)
2016-03-16 14:10:58 -07:00
Charles Hacskaylo
32a42bd679 [Frontend] CSS tweaks to fix required glyph positioning
#700
CSS modded to allow better cross-browser
positioning of 'required' form-row glyphs;
Other elem's CSS adjusted as a result;
2016-03-16 09:43:59 -07:00
Victor Woeltjen
324c2cac03 [Tree] Refresh properly on mutation
...by removing the incorrect expectation that a domain object
(and not just its model) will be passed in when mutation occurs.
Addresses #745.
2016-03-15 12:23:46 -07:00
Victor Woeltjen
42ac657105 [Tree] Add status classes during editing
Addresses #749
2016-03-15 12:16:58 -07:00
99 changed files with 3779 additions and 16252 deletions

View File

@@ -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) 20092015
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

View File

@@ -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

View File

@@ -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"
}
]
}
]
}
}
]
}
});
});

View File

@@ -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;
}
);

View File

@@ -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": {}

View File

@@ -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": [

View File

@@ -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": {}
},

View File

@@ -54,7 +54,7 @@ define([
{
"name": "Measurement",
"key": "msl.measurement",
"glyph": "T",
"glyph": "\u0054",
"model": {"telemetry": {}},
"telemetry": {
"source": "rems.source",

View File

@@ -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"
],

View File

@@ -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);
}
};
});
});

View File

@@ -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": {

View File

@@ -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'"

View File

@@ -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);
};

View File

@@ -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.",

View 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;
});

View 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;
});

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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) 20092015",
"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

View File

@@ -80,7 +80,7 @@
<glyph unicode="&#xe601;" 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="&#xe602;" 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="&#xe603;" 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="&#xe604;" 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="&#xe604;" 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="&#xe605;" 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="&#xe606;" 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="&#xe607;" 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="&#xe614;" 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="&#xe615;" 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="&#xe616;" 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="&#xe617;" 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="&#xe618;" 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="&#xe617;" 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="&#xe618;" 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="&#xe619;" 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="&#xe620;" 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="&#xe621;" 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="&#xe622;" 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="&#xe642;" 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

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -45,7 +45,6 @@
/********************************* FORMS */
@import "forms/elems";
@import "forms/selects";
@import "forms/channel-selector";
@import "forms/datetime";
@import "forms/validation";

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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%;
}

View File

@@ -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,

View File

@@ -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;
}
}
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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 () {

View File

@@ -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

View File

@@ -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;

View File

@@ -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"
]
}
],

View File

@@ -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.:

View File

@@ -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 ||

View File

@@ -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 :

View File

@@ -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;

View 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;
});

View File

@@ -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);
};
}

View File

@@ -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));
});
});

View File

@@ -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){

View File

@@ -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(
});
}
);
);

View 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);
});
});
});
});

View File

@@ -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
);
});
});
}

View File

@@ -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": [

View File

@@ -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;
});

View 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;
});

View 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);
});
});
});
});

View 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);
});
});
});
});

View File

@@ -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",

View File

@@ -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"
],

View File

@@ -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"
}
]
}

View File

@@ -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);
};

View File

@@ -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");
});
});
});

View File

@@ -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"
}
]
}
});

View File

@@ -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;
}
}
}

View File

@@ -1,4 +1,4 @@
<div ng-controller="TelemetryTableController">
<div ng-controller="HistoricalTableController">
<mct-table
headers="headers"
rows="rows"

View File

@@ -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 }}

View File

@@ -1,4 +1,4 @@
<div ng-controller="RTTelemetryTableController">
<div ng-controller="RealtimeTableController">
<mct-table
headers="headers"
rows="rows"

View File

@@ -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;
};

View File

@@ -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;
}
);

View File

@@ -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));
};
/**

View File

@@ -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;
}
);

View File

@@ -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;

View File

@@ -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];

View File

@@ -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) {

View File

@@ -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
*/
});
});
}

View File

@@ -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();
});
});

View File

@@ -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'});
});
});
});

View File

@@ -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 () {

View File

@@ -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();
});

View File

@@ -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"
}
]
}

View File

@@ -59,7 +59,7 @@
.handle {
cursor: col-resize;
&.mid {
cursor: ew-resize;
cursor: move;
}
}
}

View File

@@ -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%);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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()"
>
&#x00e9;
</span>
</span>

View File

@@ -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">&#x00e9;</span>
<span class="l-col l-col-icon l-col-link ui-symbol">&#x00e8;</span>
<span title="Resource Graphing: click a row to toggle" class="l-col l-col-icon l-plot-resource ui-symbol">&#x00e9;</span>
<span title="Activity Links" class="l-col l-col-icon l-col-link ui-symbol">&#x00e8;</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"

View File

@@ -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] || [];

View File

@@ -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;
}
);
);

View File

@@ -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();
}
});
}

View File

@@ -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));
});

View File

@@ -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();
});
});
}
);
);

View File

@@ -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();
});
});
}

View File

@@ -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>

View File

@@ -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}}

View File

@@ -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') {

View File

@@ -194,6 +194,11 @@ define(
);
});
it("invokes preventDefault on drop", function () {
callbacks.drop(mockEvent);
expect(mockEvent.preventDefault).toHaveBeenCalled();
});
});
}
);
);

View File

@@ -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;

View File

@@ -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);
});
});
}