Compare commits

...

200 Commits

Author SHA1 Message Date
Henry
b152b52ab8 Use painterro shim to fix notebook bug 2018-04-10 17:08:23 -07:00
Henry
000f179f5b Reduce frequency of template recompilation in mct-include 2018-04-10 17:05:42 -07:00
Henry
70a7e23f15 Shim Painterro to allow cleanup of event handlers 2018-04-10 17:05:40 -07:00
Deep Tailor
5f80ce9504 add functionality of being able to add buttons to the browse bar element of overlay when instantiating the overlay service 2018-04-02 15:37:29 -07:00
Deep Tailor
ef48249a27 fix checkstyle and lint 2018-04-02 13:06:45 -07:00
Deep Tailor
5ab9bd5960 fix broken mcttriggermodal tests 2018-04-02 13:04:03 -07:00
Deep Tailor
36ecf66abb abstract overlay into a service/api - to reduce code duplication
catch error produced by painterro because of async div creation by dialog service
2018-04-02 12:50:32 -07:00
Charles Hacskaylo
beebd002ac [Frontend] Notebook/Preview related sanding and polishing
Fixes #1947
- Classes for Notebook Entry button spacing;
2018-03-28 14:39:36 -07:00
Deep Tailor
a6a73126e1 code cleanup 2018-03-28 14:32:43 -07:00
Charles Hacskaylo
957a9e8eab [Frontend] Notebook/Preview related sanding and polishing
Fixes #1947
- Added new global "hide-in-t-main-view" class;
- Apply new class to Preview action to suppress
display of that button in main view of navigated object;
2018-03-27 18:41:35 -07:00
Charles Hacskaylo
c3b98890d9 [Frontend] Notebook/Preview related sanding and polishing
Fixes #1947
- Changed description for notebook-new-entry
2018-03-27 18:40:02 -07:00
Charles Hacskaylo
b8ae9fb08a [Frontend] Painterro styling and config
Fixes #1896
Fixes #1947
- Changes mainly related to toolbar styling and labels
2018-03-27 16:40:33 -07:00
Deep Tailor
91f442401d changes to browseController and NavigationService to block navigation and show preview of object when trying to navigate to object in tree in edit mode 2018-03-27 15:55:26 -07:00
Deep Tailor
261583293b dont allow preview action on objects getting edited currently 2018-03-27 13:51:26 -07:00
Deep Tailor
10a11b2597 change glyph for preview, use similar tempalte to frame.html 2018-03-27 10:45:41 -07:00
Deep Tailor
25373e0c89 fix checkstyle 2018-03-26 16:10:35 -07:00
Deep Tailor
8daada635b add preview action, working, need styling for notebook action on preview 2018-03-26 16:09:43 -07:00
Deep Tailor
e6b6bd2772 remove snapshot from frame view, add it only to overlay, change mctSnapShot to accomodate taking snaps of overlay/object view 2018-03-22 15:48:25 -07:00
Deep Tailor
796814359c remove snapshot button from frames in layout 2018-03-22 14:12:19 -07:00
Charles Hacskaylo
21294812e4 [Frontend] Adding background color to snapshot
Fixes #1896
Fixes #1947
2018-03-22 13:36:24 -07:00
Charles Hacskaylo
fca221dbb3 Merge branch 'notebook-integration-deep' of https://github.com/nasa/openmct into notebook-integration-deep 2018-03-22 13:33:46 -07:00
Deep Tailor
f1da9df37b remove snapshot class after async image render 2018-03-22 13:32:35 -07:00
Charles Hacskaylo
82c11a4fb8 Merge branch 'notebook-integration-deep' of https://github.com/nasa/openmct into notebook-integration-deep 2018-03-22 13:25:35 -07:00
Charles Hacskaylo
5edcd78ca3 [Frontend] Adding background color to snapshot
Fixes #1896
Fixes #1947
2018-03-22 13:25:28 -07:00
Deep Tailor
d8205e0a8f select correct div for snapshot 2018-03-22 13:23:08 -07:00
Charles Hacskaylo
97488cc996 Merge branch 'notebook-integration-deep' of https://github.com/nasa/openmct into notebook-integration-deep 2018-03-22 13:09:28 -07:00
Charles Hacskaylo
90fe0545d3 [Frontend] Local controls CSS added for hide/show of trash can icons
Fixes #1896
Fixes #1947
- Also updated frame.scss to use same transition timing
2018-03-22 13:08:56 -07:00
Deep Tailor
1bb6e140c1 dragging objects to notebook now only creates a link, clicking on snapshot from object view takes a snapshot of the current view, without re-rendering 2018-03-22 13:06:24 -07:00
Pete Richards
7cbeb22e1c proper shimming 2018-03-22 11:22:02 -07:00
Charles Hacskaylo
50f85dfa8e Merge branch 'notebook-integration-deep' of https://github.com/nasa/openmct into notebook-integration-deep 2018-03-22 10:56:24 -07:00
Charles Hacskaylo
bdf5b9ec1a [Frontend] Local controls CSS added for hide/show of trash can icons
Fixes #1896
Fixes #1947
- Also updated frame.scss to use same transition timing
2018-03-22 10:33:20 -07:00
Pete Richards
a0c1729df2 specify require paths for new library 2018-03-22 10:21:47 -07:00
Charles Hacskaylo
220a93e530 Merge branch 'notebook-integration-deep' of https://github.com/nasa/openmct into notebook-integration-deep 2018-03-21 22:37:48 -07:00
Deep Tailor
f9464ba065 fixes issue of overlay not closing when context menu item in clicked when viewing snapshot 2018-03-21 22:37:20 -07:00
Charles Hacskaylo
541e3d7be1 [Frontend] Painterro styling
Fixes #1896
Fixes #1947
- WIP
- Painterro styling overrides and config
2018-03-21 22:33:23 -07:00
Charles Hacskaylo
1670b8dc9e [Frontend] Painterro styling
Fixes #1896
Fixes #1947
- WIP
- Painterro styling overrides and config
- Removed commented code
2018-03-21 19:02:27 -07:00
Deep Tailor
f45198b91c fixes issue of overlay not closing when context menu item in clicked when viewing snapshot 2018-03-21 12:48:09 -07:00
Charles Hacskaylo
2a4a5e1a73 [Frontend] Snapshot styling
Fixes #1896
Fixes #1947
- Final tweaks after rebase from
notebook-integration-deep-styling
2018-03-21 11:29:39 -07:00
Charles Hacskaylo
3496a83474 Merge branch 'notebook-integration-deep' of https://github.com/nasa/openmct into notebook-integration-deep 2018-03-21 11:26:08 -07:00
Pete Richards
d8b07ac8ff Lock filesaver version (#1956)
Lock filesaver version as there have been a large number of broken
builds from what should be non-breaking version increases.

Fixes currently broken build.
2018-03-21 11:25:37 -07:00
Henry
f3a8c64af3 Updated version number for Enterprise release 2018-03-21 11:25:37 -07:00
Henry
0ee35f3f54 Removed snapshot from version number to close sprint eagle 2018-03-21 11:25:37 -07:00
Charles Hacskaylo
257e2dd67e [Frontend] WIP Snapshot styling
Fixes #1896
Fixes #1947
- Significant markup changes to ViewSnaphot.js;
- Change in imagery.scss to move non-layout styling
to appropriate class;
2018-03-21 11:01:30 -07:00
Charles Hacskaylo
8bfebc11e6 [Frontend] WIP Snapshot styling
Fixes #1896
Fixes #1947
- Significant markup changes to template in ViewSnaphot.js
- WIP!!! Keeping own topic branch for now
2018-03-20 18:01:47 -07:00
Deep Tailor
832572052d Merge branch 'master' of https://github.com/nasa/openmct into notebook-integration-deep 2018-03-19 14:53:33 -07:00
Pete Richards
00fb071fe2 Lock filesaver version (#1956)
Lock filesaver version as there have been a large number of broken
builds from what should be non-breaking version increases.

Fixes currently broken build.
2018-03-19 14:52:47 -07:00
Deep Tailor
bdcc058dc1 change snapshot library to dom-to-image 2018-03-19 13:57:00 -07:00
Deep Tailor
5d55f43d22 user findElementById in entrydnd 2018-03-19 12:24:38 -07:00
Deep Tailor
05fdfcf8e0 abstract findElementById for reusability and improve performance from O^2 to O 2018-03-19 10:34:39 -07:00
Deep Tailor
b93c1757e4 fix focus one new entry issue, cleanup of code related to finding elements, and write more reusable code 2018-03-16 11:59:42 -07:00
Victor Woeltjen
2bf6a48d49 Merge pull request #1953 from nasa/eagle-sprint-release
Eagle sprint release
2018-03-16 10:38:49 -07:00
Henry
eca3c57bd8 Updated version number for Enterprise release 2018-03-16 10:26:01 -07:00
Henry
7753703034 Removed snapshot from version number to close sprint eagle 2018-03-16 10:15:29 -07:00
Deep Tailor
6c9efcdfb9 fix (not being able to focus on content editable div to add text, while in layout)
- when in layout, the first child of the outermost div is the only one that registers a click, this was causing an issue of not being able to edit notebook entries. My fix includes finding the first child of the div that registers the click and forcing a focus event.
2018-03-15 13:40:24 -07:00
Deep Tailor
d689b7aac5 fix layout display overload, remove viewpolicy and notebookLayout.html. Fix delete error - issues found when deploying for testathon 2018-03-14 14:01:35 -07:00
Deep Tailor
7a5a3b0cf0 disable view policy - to allow layouts to function - needs more investigation 2018-03-13 11:10:44 -07:00
Henry
49759a39b8 Inlined templates for notebook 2018-03-12 18:27:09 -07:00
Deep Tailor
cc50186700 include painterro in karma config 2018-03-09 16:34:45 -08:00
Deep Tailor
1bd4fe0de2 fix export image after merging with master 2018-03-09 16:21:40 -08:00
Deep Tailor
f44bf30b02 merge master 2018-03-09 16:09:12 -08:00
Pete Richards
f9060a485d [Plugin] Add imported root plugin (#1784)
* [Plugin] Add static root plugin

Add StaticRootPlugin, which allows a file exported with the ImportExport
plugin to be mounted as a static root in Open MCT.  Allows deployers
to configure standard displays for deployments by exporting displays they
have already created.

* Include all src files
2018-03-09 12:39:25 -08:00
Deep Tailor
166cc0421d add comments, clean out some code, and fix broken tests 2018-03-08 13:13:49 -08:00
Deep Tailor
4b13d0374b fix issue of preserving line breaks when text is received from a persisted source 2018-03-08 10:57:41 -08:00
Deep Tailor
39a7f43cd0 Fixes Issue #1938 - Fixed Positon should display enumerated value (#1939)
* Fixes #1938
Fixed usage of Telemetry API, use hints to get valueMetadata, use valueMetadata to get formatter

* dont circumvent chooseTelemetryKeyToDisplay, which was causing a regression when imagery is added to fp

* fix broken tests

* pass valueMetaData to limitEvaluator.evaluate, rename #chooseTelemetryKeyToDisplay to getValueMetadata, to reflect what is returned.
update tests

* change getValueMetadata to chooseValueMetadataToDisplay
2018-03-07 13:49:11 -08:00
Pete Richards
5103207a70 Merge pull request #1940 from nasa/update-gulp-sass
Updated version range of gulp-sass to allow later versions
2018-03-06 12:58:22 -08:00
Henry
8c2cc90f04 Updated version range of gulp-sass to allow later versions 2018-03-06 09:31:09 -08:00
Pete Richards
ce431848b3 Remove example plotOptions. (#1936)
Fixes https://github.com/nasa/openmct/issues/731
2018-03-02 15:52:02 -08:00
Pete Richards
5726fe6313 new-plot import (#1557)
Merge of new plot
* Introduces new Plot object and view
* Removes Old Plot
* Add LAD support and state type to generators
* Removes Telemetry Panel Type
* Telemetry API Updates
* UTCFormat.parse: passthrough numbers
* TelemetryAPI: default request arguments
* TelemetryAPI: fix enum formatting
* Markup and styling to support new plots
2018-03-02 14:29:34 -08:00
Pegah Sarram
6145843e86 [Inspector] Add check to prevent race condition before setting the scope composition. (#1931)
Fixes #1918
2018-02-28 13:38:00 -08:00
Deep Tailor
0225cbab6a Merge pull request #1930 from nasa/navigate-via-breadcrumbs
Restore navigation via breadcrumbs
2018-02-28 13:36:16 -08:00
Pete Richards
e477beb587 Merge pull request #1875 from tobiasbrown/open1872
[DateTimePicker] Time Conductor Date Picker menu is missing background
2018-02-28 13:29:21 -08:00
Pete Richards
ee5d59024a Restore navigation via breadcrumbs
Fix navigation via inspector breadcrumbs.

Fixes https://github.com/nasa/openmct/issues/1927
2018-02-28 12:04:14 -08:00
Victor Woeltjen
5288dadafb Merge pull request #1926 from nasa/discovery-eagle
Version bump - 0.12.0 -> 0.13.1
2018-02-26 17:45:03 -08:00
Deep Tailor
02b1ec1343 remove frame layout duplicate and use frame.html 2018-02-26 14:43:24 -08:00
Henry
7f3cc09cbc Updated version number for start of release Eagle 2018-02-23 13:42:19 -08:00
Henry
94fa70abb1 Updated version number to close sprint 'Discovery One' 2018-02-23 13:32:31 -08:00
Charles Hacskaylo
ad1b2681d2 [Frontend] WIP Snapshot styling
Fixes #1896
- Better class names;
- Moved buttons in frame layout;
2018-02-22 18:17:45 -08:00
Charles Hacskaylo
9751236da5 Merge branch 'notebook-integration-deep' of https://github.com/nasa/openmct into notebook-integration-deep 2018-02-22 16:04:18 -08:00
Deep Tailor
db14b806d1 prevent multiple new notebook entry divs from being created on open overlay, instead create on initialization 2018-02-22 15:47:27 -08:00
Charles Hacskaylo
18bae253bd Merge branch 'notebook-integration-deep' of https://github.com/nasa/openmct into notebook-integration-deep 2018-02-22 15:45:34 -08:00
Charles Hacskaylo
43c9804326 [Frontend] WIP Mobile styling
Fixes #1896
- Fixed general approach to portrait orientation in
mobile/_layout.scss to use media query;
- Fixed portrait layout in _notebook_base.scss
to use media query;
2018-02-22 15:45:13 -08:00
Deep Tailor
63bc8178a9 remove duplicate code from MCTModalNotebook and roll changes into MCTTriggerModal 2018-02-22 15:38:38 -08:00
Charles Hacskaylo
3fbb1833bf [Frontend] Fix custom Selects
Fixes #1896
- Custom Selects now much more
solid, handle width compression better;
2018-02-22 14:36:29 -08:00
Charles Hacskaylo
50420a5eff [Frontend] Fixes to search control
Fixes #1896
- Search control now more robust, added
.search-filter-by-type class selector;
2018-02-22 14:35:48 -08:00
Deep Tailor
ab748026ab fix expand in layout, which was causing snapshot at expand 2018-02-22 13:28:21 -08:00
Charles Hacskaylo
f34bb48398 [Frontend] WIP Mobile styling
Fixes #1896
- phone portrait entry layout optimization
2018-02-21 19:02:38 -08:00
Charles Hacskaylo
c47defd6bc [Frontend] Mobile styling
Fixes #1896
- Mod .has-local-controls to not apply when in touch context
2018-02-21 18:31:33 -08:00
Charles Hacskaylo
3d926f2ff7 [Frontend] CSS sanding and cleanups
Fixes #1896
- Removing unused classes;
- Finessed margin and padding;
2018-02-21 18:30:35 -08:00
Pete Richards
12574a1333 Tests for Composition API providers 2018-02-20 09:40:57 -08:00
Deep Tailor
dc91a94f0e Merge pull request #1913 from nasa/layout-issue-1909
[Layout] Select fixed position view only if the parent is not selected
2018-02-15 15:20:03 -08:00
Deep Tailor
f653ff4890 added modifiedOn time for entries that are changed, and fixed issue regarding inner text being filled when new entry button clicked 2018-02-14 12:29:11 -08:00
Charles Hacskaylo
b8e8ca8332 [Frontend] CSS normalizing, apply general styles in markup
Fixes #1896
- Notebook class names more individualized;
- Apply .labeled and .has-local-controls general classes;
- Apply .s-input-inline to contenteditable div;
- Look and feel cleanups for drag area and entry elements;
2018-02-13 19:05:12 -08:00
Charles Hacskaylo
4eebec5c13 [Merge] Generalized labeled icon-* elements
Fixes #1896
2018-02-13 19:02:29 -08:00
Charles Hacskaylo
09ef7d8d38 [Merge] Generalized hover hide/show of local controls
Fixes #1896
2018-02-13 19:01:45 -08:00
Pete Richards
0243aa6584 [API] provider support for dynamic composition is optional (#1915)
All views are expected to implement dynamic composition handling
by listening for the "add" and "remove" events and then calling
"collection.load()" when they are ready to handle these events.

However, it does not make sense that every composition provider will
be dynamic, so implementing support for dynamic composition should
not be a requirement.  This commit removes that requirement.

Fixes #1914
2018-02-13 18:00:35 -08:00
Pegah Sarram
e5d869f01e [Layout] Select the fixed position view only if the parent is not selected.
Also, add mutation listener if domain object is defined to fix the TypeError.

Fixes # 1909 and #1912
2018-02-13 13:23:49 -08:00
Pegah Sarram
d4e3e6689c [Inspector] Listen for mutation and refresh composition
...so that elements pool is updated when selected object's composition changes. Fixes #1869
2018-02-12 10:49:56 -08:00
Charles Hacskaylo
d430f3e621 [Front-end] Refined styling of entry embeds
Fixes #1896
2018-02-09 16:57:47 -08:00
Deep Tailor
585f05d576 fix drag and drop, delete entries 2018-02-08 11:56:32 -08:00
Deep Tailor
54fe6d0e59 Merge branch 'notebook-integration-1896-styling' of https://github.com/nasa/openmct into notebook-integration-deep 2018-02-07 11:48:28 -08:00
Charles Hacskaylo
ab95f947e8 [Merge] WIP Mods related to MCTModalNotebook.js
Fixes #1896
Fixes #1906
2018-02-06 19:05:23 -08:00
Charles Hacskaylo
179e69867b [Merge] WIP SCSS and markup polishing
Fixes #1896
- Significant style and markup changes;
- Styles, layout, etc. relating to embed elements;
- Fixes in both notebook.html and embedControl.html;
- Class name normalization;
2018-02-06 18:15:54 -08:00
Charles Hacskaylo
b2fb958a77 [Merge] Cleanups in JS
Fixes #1896
- Removed and commented out logging statements
2018-02-06 18:12:53 -08:00
Charles Hacskaylo
3ee6a04db0 [Front-end] Notebook thematic styling; test console
Fixes #1896
- Added thematic styles and config;
- Really, really, really WIP!!
- DnD doesn't work properly, and drag to
existing entry no longer works.
2018-02-05 15:54:06 -08:00
Deep Tailor
cca8b558bd Merge branch 'master' of https://github.com/nasa/openmct into notebook-integration-deep 2018-02-05 11:54:10 -08:00
Sam Price
0363d0e8ad d3 selection filepath changed (#1898)
* d3 selection changed from build to dist.
* build to dist for test-main.js
2018-02-05 11:12:22 -08:00
Deep Tailor
3669e776a9 add parameter for background color, only change color when parameter is passed in (export image service is used in notebook which needs the background color not changed
added tests
2018-02-05 10:46:56 -08:00
Charles Hacskaylo
59fd3415d6 [Merge] JS debugging
Fixes #1896
- Really, really WIP
- DnD doesn't work properly, and drag to
existing entry no longer works.
2018-02-02 17:08:58 -08:00
Charles Hacskaylo
8aeef6ae37 [Merge] JS debugging
Fixes #1896
- Very much WIP!
2018-02-02 14:30:23 -08:00
Victor Woeltjen
5d3adc6a7f [Documentation] Add security guide (#1900)
* [Documentation] Add initial security overview content

Fixes #1833

* [Documentation] Outline security guide

* [Documentation] Retitle Security Guide

* [Documentation] Reformat security procedures

* [Documentation] Flesh out security notes

* [Documentation] Add references to Security Guide

* [Documentation] Note role of static analysis

https://github.com/nasa/openmct/pull/1900#pullrequestreview-93769470
2018-02-02 14:23:08 -08:00
Charles Hacskaylo
0543aa9521 [Merge] WIP markup and styling
Fixes #1896
- Very much WIP, attempting to convert
textarea to contenteditable;
2018-01-31 19:42:42 -08:00
Charles Hacskaylo
78ae568302 [Merge] WIP markup and styling
Fixes #1896
- Very much WIP, currently having issues with
hovering and jiggling
2018-01-30 18:46:42 -08:00
Victor Woeltjen
fad3afbd3c [Notebook] Fix require config for painterro 2018-01-30 15:46:47 -08:00
Victor Woeltjen
6f975c2d9d [Notebook] Include painterro for tests 2018-01-30 15:42:45 -08:00
Victor Woeltjen
9fd397b34e [Notebook] Run gulp fixstyle 2018-01-30 15:36:35 -08:00
Victor Woeltjen
cbddfac96f [Notebook] Fix lint issues 2018-01-30 15:31:42 -08:00
Victor Woeltjen
b5153b0f27 [Notebook] Fix lint issues 2018-01-30 15:28:23 -08:00
Victor Woeltjen
b766a22034 [Notebook] Fix lint issues 2018-01-30 15:26:35 -08:00
Victor Woeltjen
4bde1c1ab8 [Notebook] Fix lint issues 2018-01-30 15:18:47 -08:00
Victor Woeltjen
1d314a91e0 [Notebook] Fix lint issues 2018-01-30 15:17:51 -08:00
Victor Woeltjen
e370fbd6c5 [Notebook] Fix lint issues 2018-01-30 15:14:26 -08:00
Victor Woeltjen
25cce4dd0d [Notebook] Fix lint issues 2018-01-30 15:13:01 -08:00
Victor Woeltjen
78dd80d081 [Notebook] Fix lint issues 2018-01-30 15:10:16 -08:00
Victor Woeltjen
944fb5e53a [Notebook] Fix lint issues 2018-01-30 15:08:58 -08:00
Victor Woeltjen
ac7ea7ba44 [Notebook] Use dot notation instead of brackets
...for checkstyle
2018-01-30 15:05:31 -08:00
Victor Woeltjen
5d34628dd8 [Notebook] Run gulp fixstyle 2018-01-30 15:04:47 -08:00
Victor Woeltjen
c3a2ac3dd5 [Notebook] Remove extra comma 2018-01-30 15:04:25 -08:00
Victor Woeltjen
62ebcd7277 [Notebook] Use dot notation instead of brackets
...for checkstyle
2018-01-30 15:01:27 -08:00
Victor Woeltjen
79a5e479cb [Notebook] Run gulp fixstyle 2018-01-30 15:00:06 -08:00
Victor Woeltjen
d9b66e23aa [Notebook] Remove demo entries 2018-01-30 14:59:03 -08:00
Victor Woeltjen
f1c3a56147 [Notebook] Expose via openmct.plugins 2018-01-30 14:58:34 -08:00
Victor Woeltjen
02e5defbdc [Notebook] Restore original index.html 2018-01-30 14:54:08 -08:00
Victor Woeltjen
9897883335 [Notebook] Remove obsolete README 2018-01-30 14:53:47 -08:00
Victor Woeltjen
c15bb45411 [Notebook] Relocate to platform/features/notebook 2018-01-30 14:49:44 -08:00
Victor Woeltjen
a085b2a7b8 Merge remote-tracking branch 'origin/master' into notebook-integration-1896
Fixes #1896
2018-01-30 14:41:31 -08:00
Deep Tailor
c1b2db848a Merge pull request #1887 from nasa/jshint-late-def
[Code Style] Allow late definition of functions
2018-01-23 23:25:24 -08:00
Henry
5d19294c11 Disabled late definition check for functions 2018-01-18 17:23:23 -08:00
Tobias Brown
8c72729a2a [DateTimePicker] Replaced tabs with spaces
Addresses #1872
2018-01-17 09:33:28 +11:00
Deep Tailor
9b8d5f3f9c Switch to white background during export
* Defaulted background option to white for PNG/JPG export

* Attempt at fixing background colour on image output

* Reverted build location change

* WIP for white background

* WIP for white background

* Updating default colour, including saving of existing colour to restore appropriately

* Fix tests and move css change background outside the try block

* keep consistent with american english

* add method to change background color and test wether it has been called with the right params

* change color to original when save fails

Fixes #1422
2018-01-16 09:32:49 -08:00
Tobias Brown
129ab1791b [DateTimePicker] Re-added .s-menu styles removed in bc7d92ee0d
Addresses #1872
2018-01-15 12:43:16 +11:00
Charles Hacskaylo
d03f323a9b [Frontend] Support for hover on FP sub-objects in browse mode
Fixes #1849
2018-01-10 15:16:04 -08:00
Pegah Sarram
54a453e5a0 Fix checkstyle error 2018-01-10 15:16:04 -08:00
Pegah Sarram
14894cf197 [Fixed Position] Modify fixed position to use the Selection API
Change method name to shouldSelect() as requested by the reviewer.

Fix tests.

Fixes #1848
2018-01-10 15:16:04 -08:00
Even Stensberg
0c6786198a adds v8-compile-cache 2018-01-08 09:52:16 -08:00
Deep Tailor
6d077b775d fixes issue #1830
add offsetX to popupService instance in infoService, to prevent bubble from appearing under the mouse pointer, which causes interminent calls to the callback.
2018-01-08 09:50:45 -08:00
Deep Tailor
144437a06e remove commented code 2018-01-04 12:56:57 -08:00
Deep Tailor
557cd91b21 fix tests 2018-01-04 12:56:57 -08:00
Deep Tailor
39d3e92094 fix for Issue 1838
Remove isDirty check, always allow blocking popup when exiting edit mode
2018-01-04 12:56:57 -08:00
Even Stensberg
7529a86d01 update node and npm before tests 2018-01-04 10:47:14 -08:00
Even Stensberg
d34e36831c prepare -> prepublish 2018-01-04 10:47:14 -08:00
Deep Tailor
aa8fa9168a add isDefined condition in condition Evaluator which fixes Issue 1860
Add appropriate tests
Fix for isUndefined not working as well
2018-01-04 10:43:05 -08:00
Pete Richards
3f1b7e0a87 Add test for identifier generation 2018-01-03 12:11:35 -08:00
Pete Richards
5ec3b98d1c [SummaryWidget] Use objectutil to get legacy id
Use objectUtils to get a proper legacy id so that namespaces are
properly handled.  Fixes https://github.com/nasa/openmct/issues/1858
2018-01-03 12:11:35 -08:00
lunarkid
3366f21155 Merge pull request #37 from cristianmusic7/master
Naming conventions fixes
2017-12-28 12:16:27 +07:00
Cristian Franco
45d5b0752b filenames fix 2017-12-28 00:08:10 -05:00
Cristian Franco
07d7f8fd5f names and package fixes 2017-12-28 00:03:49 -05:00
lunarkid
2b7b4a552c Merge pull request #36 from cristianmusic7/master
Code issues fixed
2017-12-27 15:11:03 +07:00
Cristian Franco
3f9bad1805 Code issues fixes:
NotificationLaunchIndicator deleted.
Inappropriate modifications to domain object models fixed.
Implemented $destroy listener on entryDnd directive.
Naming conventions fixed.
Unnecessary changes made to platform handled.
Painterro dependency handled
gulp verify fix.
2017-12-27 00:26:27 -05:00
Pete Richards
1ad5094b72 Merge pull request #1847 from nasa/invalid-selector-1846
[Layout] Don't use class name to query by id
2017-12-20 14:02:21 -08:00
Victor Woeltjen
b54ee2257e [Layout] Don't use class name to query by id
...since ids may be invalid class names. Instead, use a data attribute. Fixes #1846
2017-12-20 13:42:46 -08:00
Deep Tailor
fcef4274e5 Merge pull request #1845 from nasa/timeline-selection-regression
[TIMELINE] fix selection regression in timeline
2017-12-19 13:46:20 -08:00
Pegah Sarram
744a5340d3 [TIMELINE] fix selection regression in timeline
Fixes # 1842
2017-12-19 13:05:04 -08:00
Deep Tailor
d140051054 Merge pull request #1843 from nasa/follow-bug-1836
[Timers] Fix bug in FollowIndicator
2017-12-18 15:22:35 -08:00
Victor Woeltjen
8da74f2665 [Timers] Fix bug in FollowIndicator
...by expecting new-style instead of legacy domain objects.
Fixes #1836
2017-12-18 13:28:05 -08:00
lunarkid
fec1d38f7e Merge pull request #28 from cristianmusic7/master
Test case functional and cosmetic issues fixed.
2017-12-18 10:31:31 +07:00
Cristian Franco
499655def2 updated file saver library 2017-12-17 22:09:34 -05:00
Cristian Franco
d5985b844c Merge branch 'master' of https://github.com/cristianmusic7/openmct 2017-12-15 23:56:37 -05:00
Cristian Franco
59bb5d3d55 Test cases functional and cosmetic issues fixed. 2017-12-15 23:53:14 -05:00
Cristian Franco
5b298525e3 Test case functional and cosmetic issues fixed. 2017-12-15 23:41:10 -05:00
Henry
2390278b97 [Telemetry Mean] Addressed code review issues 2017-12-11 10:44:07 -08:00
Henry
8a66731271 Added tests for MeanTelemetryProvider 2017-12-11 10:44:07 -08:00
Henry
0a9ea48355 Implemention of basic averaging telemetry filter 2017-12-11 10:44:07 -08:00
Pete Richards
01d93306f3 Fix insertion point scan 2017-12-11 10:39:02 -08:00
Pete Richards
0588f9190a Move DupeCheck inside of Collection 2017-12-11 10:39:02 -08:00
Pete Richards
1378b57567 Remove format/parse cache
Remove the cache for formatted and parsed values, as this was
a net performance loss due to a very low cache hit percentage.
2017-12-11 10:39:02 -08:00
Pete Richards
9e12886c66 Shortcut index check for append/prepend
Update the insertion point check with shortcutting behavior
for appending / prepending objects, which is the common case
for sorted inserts on initial table load (when large numbers of
records are inserted).  This allows O(1) performance for the
common case while maintaining O(log n) performance for the edge
case.
2017-12-11 10:39:02 -08:00
Pete Richards
2d352ac574 only dupe check when needed
Only enable datum dupe checking in collection after data
has been received.  This works under the assumption that a
single telemetry request will not contain duplicate elements,
thus, it is not necessary to check for dupes on the initial
request.

Improves performance when rows are sorted by a column
that has duplicate row-values.
2017-12-11 10:39:02 -08:00
Deep Tailor
284dec4903 Merge pull request #1834 from nasa/summary-widgets-ml
Summary widgets Memory Leak Fix
2017-12-07 14:19:16 -08:00
tobiasbrown
5a0656c700 [DateTime Field] Disabled autocorrect and spellcheck for datetime fields (#1769)
Addresses #1682
2017-12-07 13:29:52 -08:00
Pegah Sarram
425655bae0 [Layout] Support sub-object selection in layout (#1811)
Updates to sub object selection, first cut of selection APIs.

* [API] Add inspector view registry to register inspector view providers and show a view in the inspector.

[API] Modify the selection API to register the click event and handle the event. The API will add a class to the selected object and the immediate parent of the selected object.

[Directive] Implemenet mct-selectable directive for making an element selectable.

[Layout] Update the layout controller to use the Selection API. Also, add double click gesture to allow drilling into a selected object.

Populate the Elements pool with contained elements of the selected object. Update toolbar and inspector to listen for the changes in selection.

* [Frontend] Mods to markup and CSS for sub-object selection

* MCTSelectable allows selection in initialization, use to select on navigation

[Frontend] Show grid in first nested layout, hide from deeper nesting. Only show grids when applicable to relative selection.

* Fix checkstyle and lint errors

* Bring back the change that made mct-init-select work

* [Inspector] Make sure the right content is displayed based on whether a view provider exists or not.

* Only show table options when editing

* Make reviewers' requested changes

* Fix broken tests

* [Frontend] Cleanups and tweaks

Fixes #1811
- Cleanups between frame, editor and selecting.scss;
- Hover and selected borders visually pumped up a bit;
- Solid borders on hover and selecting when browsing;
- Dashed borders for layouts when editing;
- Fixed cursor to only show move capability when
element is selected;

* [Frontend] Tweaks to frame.no-frame layout

Fixes #1811
- Margin set to 0;
- Overflow set to hidden;

* [Frontend] Fixed position items border width fixed

Fixes #1811
- Set to 1px;

* Add tests for inspector controller and fix broken tests. Clean up code.

* [Fixed Position] Stop event propagation on click handlers in fixed position to avoid the event reaching the selection click handlers which caused issues with toolbar and selection."

* Fix tests

* Add tests

* Add test

* Remove element from document
2017-12-07 13:04:46 -08:00
Victor Woeltjen
50b4d5cb28 [Autoflow] Rewrite Autoflow Tabular using new APIs (#1816)
Rewrite Autoflow tabular using Vue and all new telemetry APIs.  Also adds LAD support to autoflow tabular. 


* [Autoflow] Add Vue dependency

...to begin refactor away from Angular, #1810

* [Autoflow] Add Vue to require config

...to support usage in refactoring Autoflow Tabular view.

* [Autoflow] Sketch in new plugin registration

* [Autoflow] Bring over template, without Angular

* [Autoflow] Add license headers

* [Autoflow] Add VueView

...to simplify addition of Vue-based views.

* [Autoflow] Add Vue bindings to template

* [Autoflow] Sketch in AutoflowTabularView

* [Autoflow] Include title for row names

* [Autoflow] Begin adding controller

* [Autoflow] Sketch in controller functionality

* [Autoflow] Support object filtering

* [Autoflow] Unlisten from controller on destroy

* [Autoflow] Track rows on an interval

* [Autoflow] Support column width changes

* [Autoflow] Expose new plugin through openmct.plugins

* [Autoflow] Fix run-time errors instantiating view

* [Autoflow] Fix row generation error

* [Autoflow] Fix row formatting

* [Autoflow] Utilize width

* [Autoflow] Update autoflow view when filter changes

* [Autoflow] Enable autoflow for telemetry panels

...in developer environment.

* [Autoflow] Bind data-value for rows

* [Autoflow] Include limit evaluations

* [Autoflow] Rename property rows to rowCount

* [Autoflow] Retain rows during update

* [Autoflow] Add bindings to clear autoflow filter

* [Autoflow] Show updated timestamp

* [Autoflow] Remove obsolete plugin

* [Autoflow] Load vue for tests

* [Autoflow] Begin adding spec for autoflow tabular plugin

* [Autoflow] Test plugin registration

* [Autoflow] Begin spec for AutoflowTabularView

* [Autoflow] Obey contract from VueView.show

...by populating a container, instead of replacing it.

* [Autoflow] Begin testing behavior

* [Autoflow] Get initial row heights

* [Autoflow] Verify unsubscription on destroy

* [Autoflow] Test column width button

* [Autoflow] Simplify controller activation/destruction

* [Autoflow] Verify data display

* [Autoflow] Test limit display

* [Autoflow] Fully initialize controller

* [Autoflow] Add missing semicolon

* [Autoflow] Separate out constants

...to access them from tests

* [Autoflow] Use constants from spec

* [Autoflow] Test autoflow behavior

* [Autoflow] Refactor test case

...to support tests for composition changes

* [Autoflow] Add test cases for composition change

* [Autoflow] Handle composition changes

* [Autoflow] Sketch in row controller

https://github.com/nasa/openmct/pull/1816/files#r153015544

* [Autoflow] Integrate row controller

https://github.com/nasa/openmct/pull/1816#pullrequestreview-79305103

* [Autoflow] Add tests for historical request

* [Autoflow] Request historical telemetry

* [Autoflow] Remove unused active flag

* [Autoflow] Clarify row destruction

...to avoid problems with binding destroy

* [Autoflow] Fix mistake in test

* [Autoflow] Simplify waiting for view updates in test

* [Autoflow] Move filtering, autoflow to view

* [Autoflow] Remove unused caching

* [Autoflow] Remove obsolete method reference

* [Autoflow] Fix lint errors

Add missing semicolon, remove unused vars

* [Autoflow] Refactor test to simplify emitting events

* [Autoflow] Emit add events during load for testing

...to simulate the actual behavior of this method.

* [Autoflow] Provide composition in mock

...to allow constructor-time usage of dependency from controller

* [Autoflow] Avoid intermittent errors

...by checking to see if tabularArea is available before
accessing its clientHeight; depending on the timing of
setInterval versus Vue's mount event, it may not be!

* [Autoflow] Use add/remove composition events from controller

...exclusively, instead of attempting to load again and triggering
an infiniute loop each time.

* [Autoflow] Test that composition does not reload

* [Autoflow] Expect identifiers for remove events

* [Autoflow] Simplify row-matching test

* [Autoflow] Combine down to a single integration test

* [Autoflow] Remove possible test race condition

* [Autoflow] Add JSDoc

* [Autoflow] Remove excess test case

...which is no longer needed after combining behavioral tests for
view into a single spec.

* [Autoflow] Remove unused destroy call

https://github.com/nasa/openmct/pull/1816/files#r154787335

* [Autoflow] Use requestAnimationFrame in tests

...to avoid brittle change detection.
https://github.com/nasa/openmct/pull/1816/files#r154785549

* [Autoflow] Use MCT instance for spies

...such that test case becomes sensitive to API changes in MCT.
2017-12-07 13:01:10 -08:00
lunarkid
1540fbcf54 Merge pull request #27 from cristianmusic7/master
CouchDB setup documentation added.
2017-11-13 14:15:26 +07:00
cristianmusic7
b15f193f5d CouchDB setup documentation added 2017-11-11 01:48:00 -05:00
cristianmusic7
bdf856526b CouchDB documentation added
Screenshots added.
2017-11-11 01:43:43 -05:00
Cristian Franco
9aafb5cbf9 CouchDB documentation added 2017-11-11 01:27:19 -05:00
lunarkid
2ccf3f0fb1 Merge pull request #26 from cristianmusic7/master
NASA review fixes:
2017-11-09 14:38:22 +07:00
Cristian Franco
46d7c117cb NASA review fixes:
css files adjusted
notebook children tree removed
embed's title links to live object
2017-11-09 01:47:01 -05:00
lunarkid
60168ea87b Merge pull request #24 from cristianmusic7/master
Painterro .map file issue fix
2017-10-26 23:52:46 +07:00
Cristian Franco
5ba3afb1a8 painterro .map file issue fixed. 2017-10-26 11:48:44 -05:00
lunarkid
3974991cfd Merge pull request #23 from cristianmusic7/master
Annotation toolbar UI style fixes, added annotation functionality on new entry dialog
2017-10-21 13:28:18 +07:00
Cristian Franco
39c5b1bbfd Annotation toolbar UI style fixes, added annotation functionality on new entry dialog 2017-10-21 01:22:48 -05:00
lunarkid
c82563c80f Merge pull request #22 from cristianmusic7/master
NASA reported issues fixed.
2017-10-20 08:11:30 +07:00
Cristian Franco
01fda1adfc NASA reported issues fixed:
objects saved in notebook, delete entry dialog, style files, and new entry from drag objects fixed.
2017-10-19 18:20:43 -05:00
lunarkid
bf5ed84f94 Merge pull request #19 from cristianmusic7/master
drag and drop style, new entry focus and delete display fixes
2017-10-15 16:45:43 +07:00
Cristian Franco
7c34f03a95 drag and drop style fix, new entry focus, delete display fix 2017-10-15 03:08:27 -05:00
lunarkid
3cf1ad8e40 Merge pull request #18 from cristianmusic7/master
Code updates, review fixes
2017-10-14 15:58:44 +07:00
Cristian Franco
3fab80c276 Code updates:
-Topcoder final fixes
-NASA review fixes
2017-10-14 02:52:07 -05:00
lunarkid
194dc43e24 Merge pull request #2 from cristianmusic7/master
Initial submission
2017-10-11 17:15:30 +07:00
Cristian Franco
16e0eeff67 NASA - OPEN MCT NOTEBOOK UI PROTOTYPE CHALLENGE
https://www.topcoder.com/challenge-details/30059614/
Initial submission
2017-10-11 02:51:39 -05:00
lunarkid
37be478a08 Merge pull request #1 from nasa/master
Add Notebook icon (#1742)
2017-10-10 11:34:53 +07:00
271 changed files with 14021 additions and 10250 deletions

View File

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

View File

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

View File

@@ -17,7 +17,7 @@
"screenfull": "^3.0.0",
"node-uuid": "^1.4.7",
"comma-separated-values": "^3.6.4",
"file-saver": "^1.3.3",
"file-saver": "1.3.3",
"zepto": "^1.1.6",
"eventemitter3": "^1.2.0",
"lodash": "3.10.1",
@@ -25,4 +25,4 @@
"html2canvas": "^0.4.1",
"moment-timezone": "^0.5.13"
}
}
}

View File

@@ -1,3 +1,11 @@
machine:
node:
version: 4.7.0
dependencies:
pre:
- npm install -g npm@latest
deployment:
production:
branch: master
@@ -16,4 +24,4 @@ test:
general:
branches:
ignore:
- gh-pages
- gh-pages

View File

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

121
docs/src/guide/security.md Normal file
View File

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

View File

@@ -30,7 +30,8 @@ define([
amplitude: 1,
period: 10,
offset: 0,
dataRateInHz: 1
dataRateInHz: 1,
phase: 0
};
function GeneratorProvider() {
@@ -50,9 +51,12 @@ define([
'amplitude',
'period',
'offset',
'dataRateInHz'
'dataRateInHz',
'phase',
];
request = request || {};
var workerRequest = {};
props.forEach(function (prop) {
@@ -67,7 +71,7 @@ define([
}
workerRequest[prop] = Number(workerRequest[prop]);
});
workerRequest.name = domainObject.name;
return workerRequest;
};

View File

@@ -0,0 +1,80 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
], function (
) {
function StateGeneratorProvider() {
}
function pointForTimestamp(timestamp, duration, name) {
return {
name: name,
utc: Math.floor(timestamp / duration) * duration,
value: Math.floor(timestamp / duration) % 2
};
}
StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var duration = domainObject.telemetry.duration * 1000;
var interval = setInterval(function () {
var now = Date.now();
callback(pointForTimestamp(now, duration, domainObject.name));
}, duration);
return function () {
clearInterval(interval);
};
};
StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.request = function (domainObject, options) {
var start = options.start;
var end = options.end;
var duration = domainObject.telemetry.duration * 1000;
if (options.strategy === 'latest' || options.size === 1) {
start = end;
}
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, duration, domainObject.name));
start += 5000;
}
return Promise.resolve(data);
};
return StateGeneratorProvider;
});

View File

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

View File

@@ -23,10 +23,12 @@
define([
"./GeneratorProvider",
"./SinewaveLimitCapability"
"./SinewaveLimitCapability",
"./StateGeneratorProvider"
], function (
GeneratorProvider,
SinewaveLimitCapability
SinewaveLimitCapability,
StateGeneratorProvider
) {
var legacyExtensions = {
@@ -46,6 +48,75 @@ define([
openmct.legacyExtension(type, extension)
})
});
openmct.types.addType("example.state-generator", {
name: "State Generator",
description: "For development use. Generates test enumerated telemetry by cycling through a given set of states",
cssClass: "icon-telemetry",
creatable: true,
form: [
{
name: "State Duration (seconds)",
control: "textfield",
cssClass: "l-input-sm l-numeric",
key: "duration",
required: true,
property: [
"telemetry",
"duration"
],
pattern: "^\\d*(\\.\\d*)?$"
}
],
initialize: function (object) {
object.telemetry = {
duration: 5,
values: [
{
key: "name",
name: "Name"
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
},
{
key: "state",
source: "value",
name: "State",
format: "enum",
enumerations: [
{
value: 0,
string: "OFF"
},
{
value: 1,
string: "ON"
}
],
hints: {
range: 1
}
},
{
key: "value",
name: "Value",
hints: {
range: 2
}
}
]
}
}
});
openmct.telemetry.addProvider(new StateGeneratorProvider());
openmct.types.addType("generator", {
name: "Sine Wave Generator",
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
@@ -99,6 +170,18 @@ define([
"dataRateInHz"
],
pattern: "^\\d*(\\.\\d*)?$"
},
{
name: "Phase (radians)",
control: "textfield",
cssClass: "l-input-sm l-numeric",
key: "phase",
required: true,
property: [
"telemetry",
"phase"
],
pattern: "^\\d*(\\.\\d*)?$"
}
],
initialize: function (object) {
@@ -107,7 +190,12 @@ define([
amplitude: 1,
offset: 0,
dataRateInHz: 1,
phase: 0,
values: [
{
key: "name",
name: "Name"
},
{
key: "utc",
name: "Time",
@@ -142,6 +230,7 @@ define([
};
}
});
openmct.telemetry.addProvider(new GeneratorProvider());
};

View File

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

View File

@@ -1,146 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define([
'legacyRegistry',
'../../platform/commonUI/browse/src/InspectorRegion',
'../../platform/commonUI/regions/src/Region'
], function (
legacyRegistry,
InspectorRegion,
Region
) {
"use strict";
/**
* Add a 'plot options' region part to the inspector region for the
* Telemetry Plot type only. {@link InspectorRegion} is a default region
* implementation that is added automatically to all types. In order to
* customize what appears in the inspector region, you can start from a
* blank slate by using Region, or customize the default inspector
* region by using {@link InspectorRegion}.
*/
var plotInspector = new InspectorRegion(),
/**
* Two region parts are defined here. One that appears only in browse
* mode, and one that appears only in edit mode. For not they both point
* to the same representation, but a different key could be used here to
* include a customized representation for edit mode.
*/
plotOptionsBrowseRegion = new Region({
name: "plot-options",
title: "Plot Options",
modes: ['browse'],
content: {
key: "plot-options-browse"
}
}),
plotOptionsEditRegion = new Region({
name: "plot-options",
title: "Plot Options",
modes: ['edit'],
content: {
key: "plot-options-browse"
}
});
/**
* Both parts are added, and policies of type 'region' will determine
* which is shown based on domain object state. A default policy is
* provided which will check the 'modes' attribute of the region part
* definition.
*/
plotInspector.addRegion(plotOptionsBrowseRegion);
plotInspector.addRegion(plotOptionsEditRegion);
legacyRegistry.register("example/plotType", {
"name": "Plot Type",
"description": "Example illustrating registration of a new object type",
"extensions": {
"types": [
{
"key": "plot",
"name": "Example Telemetry Plot",
"cssClass": "icon-telemetry-panel",
"description": "For development use. A plot for displaying telemetry.",
"priority": 10,
"delegates": [
"telemetry"
],
"features": "creation",
"contains": [
{
"has": "telemetry"
}
],
"model": {
"composition": []
},
"inspector": plotInspector,
"telemetry": {
"source": "generator",
"domains": [
{
"key": "time",
"name": "Time"
},
{
"key": "yesterday",
"name": "Yesterday"
},
{
"key": "delta",
"name": "Delta",
"format": "example.delta"
}
],
"ranges": [
{
"key": "sin",
"name": "Sine"
},
{
"key": "cos",
"name": "Cosine"
}
]
},
"properties": [
{
"name": "Period",
"control": "textfield",
"cssClass": "l-input-sm l-numeric",
"key": "period",
"required": true,
"property": [
"telemetry",
"period"
],
"pattern": "^\\d*(\\.\\d*)?$"
}
]
}
]
}
});
});

View File

@@ -58,11 +58,7 @@
position: relative;
}
.w-mct-example {
div {
margin-bottom: $interiorMarginLg;
}
}
.w-mct-example > div { margin-bottom: $interiorMarginLg; }
code,
pre {

View File

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

View File

@@ -43,6 +43,9 @@
openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.UTCTimeSystem());
openmct.install(openmct.plugins.ImportExport());
openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel"
}));
openmct.install(openmct.plugins.Conductor({
menuOptions: [
{
@@ -65,6 +68,7 @@
]
}));
openmct.install(openmct.plugins.SummaryWidget());
openmct.install(openmct.plugins.Notebook());
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
openmct.time.timeSystem('utc');
openmct.start();

View File

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

View File

@@ -37,9 +37,10 @@ requirejs.config({
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
"vue": "node_modules/vue/dist/vue.min",
"zepto": "bower_components/zepto/zepto.min",
"lodash": "bower_components/lodash/lodash",
"d3-selection": "node_modules/d3-selection/build/d3-selection.min",
"d3-selection": "node_modules/d3-selection/dist/d3-selection.min",
"d3-scale": "node_modules/d3-scale/build/d3-scale.min",
"d3-axis": "node_modules/d3-axis/build/d3-axis.min",
"d3-array": "node_modules/d3-array/build/d3-array.min",
@@ -48,7 +49,9 @@ requirejs.config({
"d3-format": "node_modules/d3-format/build/d3-format.min",
"d3-interpolate": "node_modules/d3-interpolate/build/d3-interpolate.min",
"d3-time": "node_modules/d3-time/build/d3-time.min",
"d3-time-format": "node_modules/d3-time-format/build/d3-time-format.min"
"d3-time-format": "node_modules/d3-time-format/build/d3-time-format.min",
"dom-to-image": "node_modules/dom-to-image/dist/dom-to-image.min",
"painterro": "node_modules/@cristian77/painterro/build/painterro.min"
},
"shim": {
"angular": {
@@ -66,6 +69,9 @@ requirejs.config({
"moment-duration-format": {
"deps": ["moment"]
},
"painterro": {
"exports": "Painterro"
},
"saveAs": {
"exports": "saveAs"
},
@@ -87,6 +93,9 @@ requirejs.config({
},
"d3-axis": {
"exports": "d3-axis"
},
"dom-to-image": {
"exports": "domtoimage"
}
}
});
@@ -100,6 +109,7 @@ define([
var openmct = new MCT();
openmct.legacyRegistry = defaultRegistry;
openmct.install(openmct.plugins.Plot());
if (typeof BUILD_CONSTANTS !== 'undefined') {
openmct.install(buildInfo(BUILD_CONSTANTS));

View File

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

View File

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

View File

@@ -65,7 +65,7 @@
<div class='split-pane-component t-object pane primary-pane left'>
<mct-representation mct-object="navigatedObject"
key="navigatedObject.getCapability('status').get('editing') ? 'edit-object' : 'browse-object'"
class="abs holder holder-object">
class="abs holder holder-object t-main-view">
</mct-representation>
<a class="mini-tab-icon anchor-right mobile-hide toggle-pane toggle-inspect flush-right"
title="{{ modelPaneInspect.visible()? 'Hide' : 'Show' }} the Inspection pane"

View File

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

View File

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

View File

@@ -47,8 +47,10 @@ define(
urlService,
defaultPath
) {
var initialPath = ($route.current.params.ids || defaultPath).split("/");
var currentIds;
var initialPath = ($route.current.params.ids || defaultPath).split("/"),
currentIds,
actions,
previewAction;
$scope.treeModel = {
selectedObject: undefined,
@@ -56,7 +58,22 @@ define(
navigationService.setNavigation(object, true);
},
allowSelection: function (object) {
return navigationService.shouldNavigate();
// return navigationService.shouldNavigate();
if (navigationService.anyChecksBeforeNavigation()) {
actions = actions || $scope.domainObject.getCapability('action');
previewAction = previewAction || actions.getActions({key: 'mct-preview-action'})[0];
if (previewAction && previewAction.perform) {
previewAction.perform(object);
return false;
} else {
return navigationService.shouldNavigate();
}
} else {
return true;
}
}
};

View File

@@ -109,6 +109,10 @@ define(
});
};
NavigationService.prototype.anyChecksBeforeNavigation = function () {
return this.checks.length;
};
/**
* Check if navigation should proceed. May prompt a user for input
* if any checkFns return messages. Returns true if the user wishes to
@@ -162,7 +166,6 @@ define(
*/
NavigationService.prototype.shouldWarnBeforeNavigate = function () {
var reasons = [];
this.checks.forEach(function (checkFn) {
var reason = checkFn();
if (reason) {

View File

@@ -121,7 +121,8 @@ define([
"key": "ElementsController",
"implementation": ElementsController,
"depends": [
"$scope"
"$scope",
"openmct"
]
},
{
@@ -299,9 +300,6 @@ define([
{
"key": "edit-elements",
"template": elementsTemplate,
"uses": [
"composition"
],
"gestures": [
"drop"
]
@@ -385,7 +383,10 @@ define([
]
},
{
"implementation": EditToolbarRepresenter
"implementation": EditToolbarRepresenter,
"depends": [
"openmct"
]
}
],
"constants": [

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,7 +38,7 @@ define(
* @constructor
* @implements {Representer}
*/
function EditToolbarRepresenter(scope, element, attrs) {
function EditToolbarRepresenter(openmct, scope, element, attrs) {
var self = this;
// Mark changes as ready to persist
@@ -109,6 +109,7 @@ define(
this.updateSelection = updateSelection;
this.toolbar = undefined;
this.toolbarObject = {};
this.openmct = openmct;
// If this representation exposes a toolbar, set up watches
// to synchronize with it.
@@ -146,7 +147,7 @@ define(
// Expose the toolbar object to the parent scope
initialize(definition);
// Create a selection scope
this.setSelection(new EditToolbarSelection());
this.setSelection(new EditToolbarSelection(this.openmct));
// Initialize toolbar to an empty selection
this.updateSelection([]);
};

View File

@@ -38,10 +38,24 @@ define(
* @memberof platform/commonUI/edit
* @constructor
*/
function EditToolbarSelection() {
function EditToolbarSelection(openmct) {
this.selection = [{}];
this.selecting = false;
this.selectedObj = undefined;
openmct.selection.on('change', function (selection) {
var selected = selection[0];
if (selected && selected.context.toolbar) {
this.select(selected.context.toolbar);
} else {
this.deselect();
}
if (selected && selected.context.viewProxy) {
this.proxy(selected.context.viewProxy);
}
}.bind(this));
}
/**

View File

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

View File

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

View File

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

View File

@@ -28,13 +28,25 @@ define(
var testProxy,
testElement,
otherElement,
selection;
selection,
mockSelection,
mockOpenMCT;
beforeEach(function () {
testProxy = { someKey: "some value" };
testElement = { someOtherKey: "some other value" };
otherElement = { yetAnotherKey: 42 };
selection = new EditToolbarSelection();
mockSelection = jasmine.createSpyObj("selection", [
// 'select',
'on',
'off',
'get'
]);
mockSelection.get.andReturn([]);
mockOpenMCT = {
selection: mockSelection
};
selection = new EditToolbarSelection(mockOpenMCT);
selection.proxy(testProxy);
});

View File

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

View File

@@ -41,6 +41,7 @@ define([
"./src/controllers/BannerController",
"./src/directives/MCTContainer",
"./src/directives/MCTDrag",
"./src/directives/MCTSelectable",
"./src/directives/MCTClickElsewhere",
"./src/directives/MCTResize",
"./src/directives/MCTPopup",
@@ -48,6 +49,8 @@ define([
"./src/directives/MCTSplitPane",
"./src/directives/MCTSplitter",
"./src/directives/MCTTree",
"./src/directives/MCTPreview",
"./src/actions/MCTPreviewAction",
"./src/filters/ReverseFilter",
"text!./res/templates/bottombar.html",
"text!./res/templates/controls/action-button.html",
@@ -68,6 +71,7 @@ define([
"text!./res/templates/controls/selector.html",
"text!./res/templates/controls/datetime-picker.html",
"text!./res/templates/controls/datetime-field.html",
"text!./res/templates/preview.html",
'legacyRegistry'
], function (
UrlService,
@@ -90,6 +94,7 @@ define([
BannerController,
MCTContainer,
MCTDrag,
MCTSelectable,
MCTClickElsewhere,
MCTResize,
MCTPopup,
@@ -97,6 +102,8 @@ define([
MCTSplitPane,
MCTSplitter,
MCTTree,
MCTPreview,
MCTPreviewAction,
ReverseFilter,
bottombarTemplate,
actionButtonTemplate,
@@ -117,6 +124,7 @@ define([
selectorTemplate,
datetimePickerTemplate,
datetimeFieldTemplate,
previewTemplate,
legacyRegistry
) {
@@ -328,6 +336,13 @@ define([
"$document"
]
},
{
"key": "mctSelectable",
"implementation": MCTSelectable,
"depends": [
"openmct"
]
},
{
"key": "mctClickElsewhere",
"implementation": MCTClickElsewhere,
@@ -386,6 +401,38 @@ define([
"key": "mctTree",
"implementation": MCTTree,
"depends": ['gestureService']
},
{
"key": "mctPreview",
"implementation": MCTPreview,
"depends": [
"$rootScope",
"$document",
"exportImageService",
"dialogService",
"notificationService"
]
}
],
"actions": [
{
"key": "mct-preview-action",
"implementation": MCTPreviewAction,
"name": "Preview",
"cssClass": "hide-in-t-main-view icon-eye-open",
"description": "Preview in large dialog",
"category": [
"contextual",
"view-control"
],
"depends": [
"$compile",
"$rootScope",
"dialogService",
"notificationService",
"linkService"
],
"priority": "preferred"
}
],
"constants": [
@@ -501,6 +548,10 @@ define([
{
"key": "object-inspector",
"template": objectInspectorTemplate
},
{
"key": "mct-preview",
"template": previewTemplate
}
],
"controls": [

View File

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

View File

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

View File

@@ -40,7 +40,7 @@
* Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json
* to generate font files
*/
font-family: 'symbolsfont 12px';
font-family: 'symbolsfont-12px';
src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot');
src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot?#iefix') format('embedded-opentype'),
url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.woff') format('woff'),
@@ -225,7 +225,8 @@ a.disabled {
}
.hide,
.hidden {
.hidden,
.t-main-view .hide-in-t-main-view {
display: none !important;
}
@@ -248,6 +249,12 @@ a.disabled {
color: rgba(#fff, 0.2);
}
.comma-list span {
&:not(:first-child) {
&:before { content: ', '; }
}
}
.test-stripes {
@include bgDiagonalStripes();
}

View File

@@ -33,6 +33,14 @@
}
}
[class*="icon-"].labeled {
// Moved from .s-button and generalized
&:before {
// Fend off label from icon when it's included
margin-right: $interiorMarginSm;
}
}
/************************** CHAR UNICODES */
$glyph-icon-alert-rect: '\e900';

View File

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

View File

@@ -53,6 +53,7 @@
.l-inspector-part {
box-sizing: border-box;
padding-right: $interiorMargin;
.tree .form {
margin-left: $treeVCW + $interiorMarginLg;
}
@@ -78,6 +79,7 @@
}
}
.form-row {
// To be replaced with .inspector-config, see below.
@include align-items(center);
border: none !important;
margin-bottom: 0 !important;
@@ -99,15 +101,12 @@
position: relative;
}
ul li {
margin-bottom: $interiorMarginLg;
}
em.t-inspector-part-header {
border-radius: $basicCr;
background-color: $colorInspectorSectionHeaderBg;
color: $colorInspectorSectionHeaderFg;
margin-bottom: $interiorMargin;
margin-top: $interiorMarginLg;
//margin-bottom: $interiorMargin;
padding: floor($formTBPad * .75) $formLRPad;
text-transform: uppercase;
}
@@ -201,3 +200,102 @@ mct-representation:not(.s-status-editing) .l-inspect {
pointer-events: inherit;
}
}
// NEW COMPACT FORM, FOR USE IN INSPECTOR
// ul > li > label, control
// Make a new UL for each form section
// Allow control-first, controls-below
.l-inspect .tree ul li,
.inspector-config ul li {
padding: 2px 0;
}
.inspector-config {
$labelW: 40%;
$minW: $labelW;
ul {
margin-bottom: $interiorMarginLg;
li {
@include display(flex);
@include flex-wrap(wrap);
@include align-items(center);
label,
.control {
@include display(flex);
min-width: $minW;
}
label {
line-height: inherit;
padding: $interiorMarginSm 0;
width: $labelW;
}
.control {
@include flex-grow(1);
}
&:not(.section-header) {
&:not(.connects-to-previous) {
//border-top: 1px solid $colorFormLines;
}
}
&.connects-to-previous {
padding-top: 0 !important;
}
&.section-header {
margin-top: $interiorMarginLg;
border-top: 1px solid $colorFormLines;
}
&.controls-first {
.control {
@include flex-grow(0);
margin-right: $interiorMargin;
min-width: 0;
order: 1;
width: auto;
}
label {
@include flex-grow(1);
order: 2;
width: auto;
}
}
&.controls-under {
display: block;
.control, label {
display: block;
width: auto;
}
ul li {
border-top: none !important;
padding: 0;
}
}
}
}
.form-error {
// Block element that visually flags an error and contains a message
background-color: $colorFormFieldErrorBg;
color: $colorFormFieldErrorFg;
border-radius: $basicCr;
display: block;
padding: 1px 6px;
&:before {
content: $glyph-icon-alert-triangle;
display: inline;
font-family: symbolsfont;
margin-right: $interiorMarginSm;
}
}
}
.tree .inspector-config {
margin-left: $treeVCW + $interiorMarginLg;
}

View File

@@ -70,6 +70,7 @@
@import "fixed-position";
@import "lists/tabular";
@import "plots/plots-main";
@import "plots/legend";
@import "iframe";
@import "views";
@import "items/item";

View File

@@ -299,7 +299,7 @@
color: $ic;
}
@if $bgHov != none {
&:not(.disabled):hover {
&:not([disabled="true"]):not(.disabled):hover {
background: $bgHov;
color: $fgHov;
>.icon,

View File

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

View File

@@ -270,37 +270,4 @@
@extend .s-summary-widget;
@extend .l-summary-widget;
padding: $interiorMarginSm $interiorMargin;
}
// Hide and show elements in the rule-header on hover
.l-widget-rule,
.l-widget-test-data-item {
.grippy,
.l-rule-action-buttons-wrapper,
.l-condition-action-buttons-wrapper,
.l-widget-test-data-item-action-buttons-wrapper {
@include trans-prop-nice($props: opacity, $dur: 500ms);
opacity: 0;
}
&:hover {
.grippy,
.l-rule-action-buttons-wrapper,
.l-widget-test-data-item-action-buttons-wrapper {
@include trans-prop-nice($props: opacity, $dur: 0);
opacity: 1;
}
}
.l-rule-action-buttons-wrapper {
.t-delete {
margin-left: 10px;
}
}
.t-condition {
&:hover {
.l-condition-action-buttons-wrapper {
@include trans-prop-nice($props: opacity, $dur: 0);
opacity: 1;
}
}
}
}

View File

@@ -34,11 +34,6 @@ $pad: $interiorMargin * $baseRatio;
line-height: $btnStdH;
padding: 0 $pad;
&.labeled:before {
// Icon when it's included
margin-right: $interiorMarginSm;
}
&.lg {
font-size: 1rem;
}
@@ -59,6 +54,10 @@ $pad: $interiorMargin * $baseRatio;
.label, .title-label { display: none; }
}
&[disabled="true"] {
opacity: 0.3;
}
&.pause-play {
@extend .icon-pause;
&.paused {

View File

@@ -139,7 +139,6 @@
}
.s-local-controls {
@include trans-prop-nice(opacity);
font-size: 0.7rem;
&.s-wrapper-transluc {
// Semi-opaque wrapper to visually distinguish a control
@@ -150,6 +149,39 @@
}
}
.has-local-controls {
.local-control {
@include trans-prop-nice($props: opacity, $dur: 250ms);
opacity: 0;
}
&:hover {
.local-control {
@include trans-prop-nice($props: opacity, $dur: 10ms);
opacity: 1;
}
}
}
/******************************************************** VIEW CONTROLS */
// Expand/collapse > and v arrows, used in tree and plot legend
// Moved this over from a tree-only context 5/18/17
.view-control {
@extend .ui-symbol;
cursor: pointer;
height: 1em; width: 1em;
line-height: inherit;
&:before {
position: absolute;
@include trans-prop-nice(transform, 100ms);
content: $glyph-icon-arrow-right;
@include transform-origin(center);
}
&.expanded:before {
@include transform(rotate(90deg));
}
}
/******************************************************** CUSTOM CHECKBOXES */
label.checkbox.custom,
label.radio.custom {
@@ -318,7 +350,7 @@ input[type="text"].s-input-inline,
@include btnSubtle($bg: $colorSelectBg);
@extend .icon-arrow-down; // Context arrow
display: inline-block;
padding: 0 $interiorMargin;
line-height: 180%;
overflow: hidden;
position: relative;
select {
@@ -329,8 +361,8 @@ input[type="text"].s-input-inline,
color: $colorSelectFg;
cursor: pointer;
border: none !important;
padding: 4px 25px 2px 0px;
width: 130%;
padding: 0 20px 0 $interiorMargin;
width: 100%;
option {
margin: $interiorMargin 0; // Firefox
}
@@ -339,6 +371,7 @@ input[type="text"].s-input-inline,
@include transform(translateY(-50%));
color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
display: block;
font-size: 0.8em;
pointer-events: none;
position: absolute;
right: $interiorMargin;

View File

@@ -77,6 +77,14 @@
position: relative;
}
.s-menu {
border-radius: $basicCr;
@include containerSubtle($colorMenuBg, $colorMenuFg);
@include boxShdw($shdwMenu);
@include txtShdw($shdwMenuText);
padding: $interiorMarginSm 0;
}
.menu {
border-radius: $basicCr;
@include containerSubtle($colorMenuBg, $colorMenuFg);

View File

@@ -398,10 +398,6 @@ body.desktop .t-message-list {
.object-header {
.t-object-alert {
display: inline;
&.t-alert-unsynced {
@extend .icon-alert-triangle;
color: $colorPausedBg;
}
}
}
}

View File

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

View File

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

View File

@@ -11,7 +11,6 @@
}
min-width: 150px;
.l-image-main {
background-color: $colorPlotBg;
margin-bottom: $interiorMargin;
}
.l-image-main-controlbar {
@@ -76,6 +75,7 @@
}
.s-image-main {
background-color: $colorPlotBg;
border: 1px solid transparent;
&.paused {
@extend .s-unsynced;

View File

@@ -131,16 +131,18 @@ body.mobile {
}
}
body.phone.portrait {
.pane-tree-showing {
.pane.left.treeview {
width: $proporMenuOnly !important;
}
.pane.right.items {
left: 0 !important;
@include transform(translateX($proporMenuOnly));
.holder-object-and-inspector {
opacity: 0;
@include phonePortrait() {
body.phone {
.pane-tree-showing {
.pane.left.treeview {
width: $proporMenuOnly !important;
}
.pane.right.items {
left: 0 !important;
@include transform(translateX($proporMenuOnly));
.holder-object-and-inspector {
opacity: 0;
}
}
}
}

View File

@@ -34,18 +34,7 @@ body.touch {
line-height: $mobileTreeItemH !important;
.view-control {
font-size: 1em;
margin-right: $interiorMargin;
width: ceil($mobileTreeItemH * 0.75);
&.has-children {
&:before {
content: $glyph-icon-arrow-down;
left: 50%;
@include transform(translateX(-50%) rotate(-90deg));
}
&.expanded:before {
@include transform(translateX(-50%) rotate(0deg));
}
}
width: ceil($mobileTreeItemH * 0.5);
}
.t-object-label {
line-height: inherit;

View File

@@ -0,0 +1,208 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
.gl-plot {
.gl-plot-legend {
min-height: $plotLegendH;
.view-control {
font-size: 1em;
margin-right: $interiorMarginSm;
}
table {
table-layout: fixed;
tr {
display: table-row;
}
th,
td {
@include ellipsize(); // Note: this won't work if table-layout uses anything other than fixed.
display: table-cell;
padding: 1px 3px; // Tighter than standard tabular padding
}
}
&.hover-on-plot {
// User is hovering over the plot to get a value at a point
.hover-value-enabled {
background-color: $legendHoverValueBg;
border-radius: $smallCr;
padding: 0 $interiorMarginSm;
&:before {
opacity: 0.5;
}
&.cursor-hover,
.value-to-display-nearestTimestamp,
.value-to-display-nearestValue
{
@extend .icon-crosshair-12px;
&:before {
font-size: 9px;
}
}
&.value-to-display-min:before {
content: 'MIN ';
}
&.value-to-display-max:before {
content: 'MAX ';
}
}
}
}
&.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; }
&.plot-legend-expanded .plot-wrapper-collapsed-legend { display: none; }
/***************** GENERAL STYLES, ALL STATES */
.plot-legend-item {
// General styles for legend items, both expanded and collapsed legend states
.plot-series-color-swatch {
border-radius: $smallCr;
border: 1px solid $colorBodyBg;
display: inline-block;
height: $plotSwatchD;
width: $plotSwatchD;
}
.plot-series-name {
display: inline;
}
.plot-series-value {
@include ellipsize();
}
}
/***************** GENERAL STYLES, COLLAPSED */
&.plot-legend-collapsed {
// .plot-legend-item is a span of spans.
&.plot-legend-top .gl-plot-legend { margin-bottom: $interiorMargin; }
&.plot-legend-bottom .gl-plot-legend { margin-top: $interiorMargin; }
&.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; }
&.plot-legend-left .gl-plot-legend { margin-right: $interiorMargin; }
.plot-legend-item {
display: flex;
align-items: center;
&:not(:first-child) {
margin-left: $interiorMarginLg;
}
.plot-series-swatch-and-name,
.plot-series-value {
@include ellipsize();
flex: 1 1 auto;
}
.plot-series-swatch-and-name {
margin-right: $interiorMarginSm;
}
.plot-series-value {
text-align: left;
width: 170px;
}
}
}
/***************** GENERAL STYLES, EXPANDED */
&.plot-legend-expanded {
.gl-plot-legend {
max-height: 70%;
}
.plot-wrapper-expanded-legend {
overflow-y: auto;
}
&.plot-legend-top .gl-plot-legend {
margin-bottom: $interiorMargin;
}
&.plot-legend-bottom .gl-plot-legend {
margin-top: $interiorMargin;
}
}
/***************** TOP OR BOTTOM */
&.plot-legend-top,
&.plot-legend-bottom {
// General styles when legend is on the top or bottom
@extend .l-flex-col;
&.plot-legend-collapsed {
// COLLAPSED ON TOP OR BOTTOM
.plot-wrapper-collapsed-legend {
display: flex;
flex: 1 1 auto;
overflow: hidden;
}
}
}
/***************** EITHER SIDE */
&.plot-legend-left,
&.plot-legend-right {
@extend .l-flex-row;
// If the legend is expanded, use flex-col instead so that the legend gets the width it needs.
&.plot-legend-expanded {
// EXPANDED, ON EITHER SIDE
@extend .l-flex-col;
}
&.plot-legend-collapsed {
// COLLAPSED, ON EITHER SIDE
.gl-plot-legend {
max-height: inherit;
width: 25%;
}
.plot-wrapper-collapsed-legend {
display: flex;
flex-flow: column nowrap;
min-width: 0;
flex: 1 1 auto;
overflow-y: auto;
}
.plot-legend-item {
margin-bottom: 1px;
margin-left: 0;
flex-wrap: wrap;
.plot-series-swatch-and-name {
flex: 0 1 auto;
min-width: 20%;
}
.plot-series-value {
flex: 0 1 auto;
width: auto;
}
}
}
}
/***************** ON BOTTOM OR RIGHT */
&.plot-legend-right:not(.plot-legend-expanded),
&.plot-legend-bottom {
.gl-plot-legend {
order: 2;
}
.plot-wrapper-axis-and-display-area {
order: 1;
}
}
}

View File

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

View File

@@ -34,7 +34,7 @@
$iconEdgeM: 4px;
$iconD: $treeSearchInputBarH - ($iconEdgeM*2);
@extend .icon-magnify;
font-size: 0.8em;
font-size: 0.8rem;
position: relative;
.search-input {
@@ -60,7 +60,7 @@
position: relative;
width: 100%;
padding-left: $iconD + $interiorMargin !important;
padding-right: ($iconD * 2) + ($interiorMargin * 2) !important;
padding-right: $iconD + $interiorMargin !important;
// Make work for mct-control textfield
input {
@@ -82,8 +82,7 @@
}
.clear-input {
// Hiding for now with addition of Cancel button
right: $iconD + $interiorMargin;
right: $interiorMargin;
// Icon is visible only when there is text input
visibility: hidden;
@@ -98,16 +97,25 @@
}
}
.menu-icon {
// 'v' invoke menu icon
font-size: 0.8em;
padding-right: $iconEdgeM;
right: $iconEdgeM;
text-align: right;
&:hover {
color: pullForward($colorInputIcon, 10%);
}
}
&.search-filter-by-type {
.search-input {
padding-right: ($iconD * 2) + ($interiorMargin * 2) !important; // Allow room for menu-icon
}
.menu-icon {
// 'v' invoke menu icon for filtering by type
font-size: 0.8em;
padding-right: $iconEdgeM;
right: $iconEdgeM;
text-align: right;
&:hover {
color: pullForward($colorInputIcon, 10%);
}
}
.clear-input {
right: $iconD + $interiorMargin;
}
}
.search-menu-holder {
float: right;

View File

@@ -23,7 +23,7 @@
ul.tree {
@include menuUlReset();
@include user-select(none);
li {
> li {
display: block;
position: relative;
}
@@ -53,12 +53,10 @@ ul.tree {
.view-control {
color: $colorItemTreeVC;
margin-right: $interiorMargin;
height: 100%;
line-height: inherit;
width: $treeVCW;
&:before { display: none; }
&.has-children {
&:before { display: block; }
&:before { display: block; }
&.no-children {
&:before { display: none; }
}
}

View File

@@ -26,12 +26,10 @@
z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
&:not(.no-frame) {
background: $colorBodyBg;
border: 1px solid $bc;
&:hover {
border-color: lighten($bc, 10%);
}
border-color: $bc;
}
}
.object-browse-bar {
font-size: 0.75em;
height: $ohH;
@@ -92,9 +90,9 @@
&.no-frame {
background: transparent !important;
border: none !important;
border-color: transparent;
.object-browse-bar .right {
$m: 0; // $interiorMarginSm;
$m: 0;
background: rgba(black, 0.3);
border-radius: $basicCr;
padding: $interiorMarginSm;
@@ -104,7 +102,7 @@
}
&.t-frame-outer > .t-rep-frame {
&.contents {
$m: 2px;
$m: 0px;
top: $m;
right: $m;
bottom: $m;
@@ -115,6 +113,7 @@
display: none;
}
> .object-holder.abs {
overflow: hidden;
top: 0 !important;
}
}
@@ -159,6 +158,7 @@ body.desktop .frame {
// Hide local controls initially and show it them on hover when they're in an element that's in a frame context
// Frame template is used because we need to target the lowest nested frame
.object-browse-bar .btn-bar {
@include trans-prop-nice($props: opacity, $dur: 250ms);
opacity: 0;
pointer-events: none;
}
@@ -167,6 +167,7 @@ body.desktop .frame {
// Handles the case where we have layouts in layouts.
&:hover > .object-browse-bar {
.btn-bar {
@include trans-prop-nice($props: opacity, $dur: 10ms);
opacity: 1;
pointer-events: inherit;
}

View File

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

View File

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

View File

@@ -0,0 +1,45 @@
<!--
Open MCT, Copyright (c) 2014-2017, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="t-frame-inner abs t-object-type-{{ domainObject.getModel().type }}" mct-preview>
<div class="abs object-browse-bar l-flex-row">
<div class="left flex-elem l-flex-row grows">
<mct-representation
key="'object-header-frame'"
mct-object="domainObject"
class="l-flex-row flex-elem object-header grows">
</mct-representation>
</div>
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<mct-representation
key="'switcher'"
ng-model="representation"
mct-object="domainObject">
</mct-representation>
</div>
</div>
<div class="abs object-holder">
<mct-representation
key="representation.selected.key"
mct-object="representation.selected.key && domainObject">
</mct-representation>
</div>
</div>

View File

@@ -0,0 +1,66 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
var PREVIEW_TEMPLATE = '<mct-representation key="\'mct-preview\'"' +
'class="t-rep-frame holder"' +
'mct-object="selObj">' +
'</mct-representation>';
function MCTPreview($compile,$rootScope,dialogService,notificationService,linkService,context) {
context = context || {};
this.domainObject = context.selectedObject || context.domainObject;
this.dialogService = dialogService;
this.notificationService = notificationService;
this.linkService = linkService;
this.$rootScope = $rootScope;
this.$compile = $compile;
}
MCTPreview.prototype.perform = function (object) {
var domainObj = object || this.domainObject,
rootScope = this.$rootScope;
rootScope.newEntryText = '';
this.$rootScope.selObj = domainObj;
this.$rootScope.selValue = "";
var newScope = rootScope.$new();
newScope.selObj = domainObj;
newScope.selValue = "";
this.$compile(PREVIEW_TEMPLATE)(newScope);
};
MCTPreview.appliesTo = function (context) {
var domainObject = (context || {}).domainObject,
status = domainObject.getCapability('status');
return !(status && status.get('editing'));
};
return MCTPreview;
}
);

View File

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

View File

@@ -0,0 +1,67 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['zepto', '../services/Overlay'], function ($, Overlay) {
/**
*/
function MCTPreview($rootScope,$document,exportImageService,dialogService,notificationService) {
function link($scope, $element, $attrs) {
var actions = $scope.domainObject.getCapability('action'),
notebookAction = actions.getActions({key: 'notebook-new-entry'})[0];
var notebookButton = notebookAction ?
[
{
class: 'icon-notebook new-notebook-entry',
title: 'New Notebook Entry',
clickHandler: function (event) {
event.stopPropagation();
notebookAction.perform();
}
}
] : [];
var overlayService = new Overlay({
$document: $document,
$element: $element[0],
$scope: $scope,
browseBarButtons: notebookButton
});
overlayService.toggleOverlay();
$scope.$on('$destroy', function () {
$element.remove();
});
}
return {
restrict: 'A',
link: link
};
}
return MCTPreview;
});

View File

@@ -0,0 +1,60 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* The mct-selectable directive allows selection functionality
* (click) to be attached to specific elements.
*
* @memberof platform/commonUI/general
* @constructor
*/
function MCTSelectable(openmct) {
// Link; install event handlers.
function link(scope, element, attrs) {
var removeSelectable = openmct.selection.selectable(
element[0],
scope.$eval(attrs.mctSelectable),
attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
);
scope.$on("$destroy", function () {
removeSelectable();
});
}
return {
// mct-selectable only makes sense as an attribute
restrict: "A",
// Link function, to install event handlers
link: link
};
}
return MCTSelectable;
}
);

View File

@@ -0,0 +1,191 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* Module defining OverlayService. Created by deeptailor on 03/29/2018
*/
define(['zepto'], function ($) {
var OVERLAY_TEMPLATE = '' +
' <div class="abs blocker"></div>' +
' <div class="abs outer-holder">' +
' <a class="close icon-x-in-circle"></a>' +
' <div class="abs inner-holder l-flex-col">' +
' <div class="t-contents flex-elem holder grows"></div>' +
' <div class="bottom-bar flex-elem holder">' +
' <a class="t-done s-button major">Done</a>' +
' </div>' +
' </div>' +
' </div>';
/*
* An Overlay Service when instantiated creates an overlay dialog.
* @param {Object} options The options object required to instantiate the overlay service
* options = {
* $document: document object,
* $scope: angular $scope object,
* element: node to be injected into overlay as a view,
* overlayWillMount: callback executed before overlay is injected,
* overlayWillUnmount: callback executed before overlay is removed,
* overlayDidMount: callback executed after overlay is injected,
* overlayDidUnmount: callback executed after overlay is removed
* browseBarButtons: an array of desired buttons to be added to the browse bar of the overlay.
* the array should consist of button objects containing:
* a) class - css class to be added to the button div
* b) title - desired button title
* c) clickHandler - callback to be added to the click event listener of the button
* }
* $document, $scope and element are required
*/
function Overlay(options) {
this.element = options.$element;
this.document = options.$document[0];
this.$scope = options.$scope;
this.overlayWillMount = options.overlayWillMount;
this.overlayWillUnmount = options.overlayWillUnmount;
this.overlayDidMount = options.overlayDidMount;
this.overlayDidUnmount = options.overlayDidUnmount;
this.browseBarButtons = options.browseBarButtons || [];
this.buttons = [];
this.openOverlay = this.openOverlay.bind(this);
this.closeOverlay = this.closeOverlay.bind(this);
this.toggleOverlay = this.toggleOverlay.bind(this);
this.removeButtons = this.removeButtons.bind(this);
this.isOverlayOpen = false;
}
Overlay.prototype.openOverlay = function () {
if (this.overlayWillMount && typeof this.overlayWillMount === 'function') {
this.overlayWillMount();
}
this.overlay = this.document.createElement('div');
$(this.overlay).addClass('abs overlay l-large-view');
this.overlay.innerHTML = OVERLAY_TEMPLATE;
this.overlayContainer = this.overlay.querySelector('.t-contents');
this.closeButton = this.overlay.querySelector('a.close');
this.closeButton.addEventListener('click', this.toggleOverlay);
this.doneButton = this.overlay.querySelector('a.t-done');
this.doneButton.addEventListener('click', this.toggleOverlay);
this.blocker = this.overlay.querySelector('.abs.blocker');
this.blocker.addEventListener('click', this.toggleOverlay);
this.document.body.appendChild(this.overlay);
this.overlayContainer.appendChild(this.element);
this.browseBar = this.overlay.querySelector('.object-browse-bar .right');
if (this.browseBarButtons && Array.isArray(this.browseBarButtons)) {
this.browseBarButtons.forEach(function (buttonObject) {
var button = newButtonTemplate(buttonObject.class, buttonObject.title);
this.browseBar.prepend(button);
button.addEventListener('click', buttonObject.clickHandler);
this.buttons.push(button);
}.bind(this));
}
if (this.overlayDidMount && typeof this.overlayDidMount === 'function') {
this.overlayDidMount();
}
};
Overlay.prototype.closeOverlay = function () {
if (this.overlayWillUnmount && typeof this.overlayWillUnmount === 'function') {
this.overlayWillUnmount();
}
this.overlayContainer.removeChild(this.element);
this.document.body.removeChild(this.overlay);
this.closeButton.removeEventListener('click', this.toggleOverlay);
this.closeButton = undefined;
this.doneButton.removeEventListener('click', this.toggleOverlay);
this.doneButton = undefined;
this.blocker.removeEventListener('click', this.toggleOverlay);
this.blocker = undefined;
this.overlayContainer = undefined;
this.overlay = undefined;
// if (this.notebookButton) {
// this.browseBar.removeChild(this.notebookButton);
// this.notebookButton.remove();
// this.notebookButton = undefined;
// }
this.removeButtons();
if (this.overlayDidUnmount && typeof this.overlayDidUnmount === 'function') {
this.overlayDidUnmount();
}
};
Overlay.prototype.toggleOverlay = function (event) {
if (event) {
event.stopPropagation();
}
if (!this.isOverlayOpen) {
this.openOverlay();
this.isOverlayOpen = true;
} else {
this.closeOverlay();
this.isOverlayOpen = false;
}
};
Overlay.prototype.removeButtons = function () {
this.buttons.forEach(function (button) {
button.remove();
}.bind(this));
this.buttons = [];
};
function newButtonTemplate(classString, title) {
var NEW_BUTTON_TEMPLATE = '<a class="s-button labeled' + classString + '">' +
'<span class="title-label">' + title + '</span>' +
'</a>';
var button = document.createElement('div');
$(button).addClass('notebook-button-container holder flex-elem');
button.innerHTML = NEW_BUTTON_TEMPLATE;
return button;
}
return Overlay;
});

View File

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

View File

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

View File

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

View File

@@ -38,7 +38,8 @@ define([
"implementation": InspectorController,
"depends": [
"$scope",
"policyService"
"openmct",
"$document"
]
}
],

View File

@@ -21,44 +21,73 @@
*****************************************************************************/
define(
['../../browse/src/InspectorRegion'],
function (InspectorRegion) {
[],
function () {
/**
* The InspectorController adds region data for a domain object's type
* to the scope.
* The InspectorController listens for the selection changes and adds the selection
* object to the scope.
*
* @constructor
*/
function InspectorController($scope, policyService) {
var domainObject = $scope.domainObject,
typeCapability = domainObject.getCapability('type'),
statusListener;
function InspectorController($scope, openmct, $document) {
var self = this;
self.$scope = $scope;
/**
* Filters region parts to only those allowed by region policies
* @param regions
* @returns {{}}
* Callback handler for the selection change event.
* Adds the selection object to the scope. If the selected item has an inspector view,
* it puts the key in the scope. If provider view exists, it shows the view.
*/
function filterRegions(inspector) {
//Dupe so we're not modifying the type definition.
return inspector.regions && inspector.regions.filter(function (region) {
return policyService.allow('region', region, domainObject);
});
function setSelection(selection) {
if (selection[0]) {
var view = openmct.inspectorViews.get(selection);
var container = $document[0].querySelectorAll('.inspector-provider-view')[0];
container.innerHTML = "";
if (view) {
self.providerView = true;
view.show(container);
} else {
self.providerView = false;
var selectedItem = selection[0].context.oldItem;
if (selectedItem) {
$scope.inspectorKey = selectedItem.getCapability("type").typeDef.inspector;
}
}
}
self.$scope.selection = selection;
}
function setRegions() {
$scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
}
openmct.selection.on("change", setSelection);
setSelection(openmct.selection.get());
statusListener = domainObject.getCapability("status").listen(setRegions);
$scope.$on("$destroy", function () {
statusListener();
openmct.selection.off("change", setSelection);
});
setRegions();
}
/**
* Gets the selected item.
*
* @returns a domain object
*/
InspectorController.prototype.selectedItem = function () {
return this.$scope.selection[0].context.oldItem;
};
/**
* Checks if a provider view exists.
*
* @returns 'true' if provider view exists, 'false' otherwise
*/
InspectorController.prototype.hasProviderView = function () {
return this.providerView;
};
return InspectorController;
}
);

View File

@@ -27,82 +27,93 @@ define(
describe("The inspector controller ", function () {
var mockScope,
mockDomainObject,
mockTypeCapability,
mockTypeDefinition,
mockPolicyService,
mockStatusCapability,
capabilities = {},
controller;
mockOpenMCT,
mockSelection,
mockInspectorViews,
mockTypeDef,
controller,
container,
$document = [],
selectable = [];
beforeEach(function () {
mockTypeDefinition = {
inspector:
{
'regions': [
{'name': 'Part One'},
{'name': 'Part Two'}
]
}
mockTypeDef = {
typeDef: {
inspector: "some-key"
}
};
mockTypeCapability = jasmine.createSpyObj('typeCapability', [
'getDefinition'
]);
mockTypeCapability.getDefinition.andReturn(mockTypeDefinition);
capabilities.type = mockTypeCapability;
mockStatusCapability = jasmine.createSpyObj('statusCapability', [
'listen'
]);
capabilities.status = mockStatusCapability;
mockDomainObject = jasmine.createSpyObj('domainObject', [
'getCapability'
]);
mockDomainObject.getCapability.andCallFake(function (name) {
return capabilities[name];
});
mockPolicyService = jasmine.createSpyObj('policyService', [
'allow'
]);
mockDomainObject.getCapability.andReturn(mockTypeDef);
mockScope = jasmine.createSpyObj('$scope',
['$on']
['$on', 'selection']
);
mockScope.domainObject = mockDomainObject;
selectable[0] = {
context: {
oldItem: mockDomainObject
}
};
mockSelection = jasmine.createSpyObj("selection", [
'on',
'off',
'get'
]);
mockSelection.get.andReturn(selectable);
mockInspectorViews = jasmine.createSpyObj('inspectorViews', ['get']);
mockOpenMCT = {
selection: mockSelection,
inspectorViews: mockInspectorViews
};
container = jasmine.createSpy('container', ['innerHTML']);
$document[0] = jasmine.createSpyObj("$document", ['querySelectorAll']);
$document[0].querySelectorAll.andReturn([container]);
controller = new InspectorController(mockScope, mockOpenMCT, $document);
});
it("filters out regions disallowed by region policy", function () {
mockPolicyService.allow.andReturn(false);
controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.regions.length).toBe(0);
it("listens for selection change event", function () {
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
expect(controller.selectedItem()).toEqual(mockDomainObject);
var mockItem = jasmine.createSpyObj('domainObject', [
'getCapability'
]);
mockItem.getCapability.andReturn(mockTypeDef);
selectable[0].context.oldItem = mockItem;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.selectedItem()).toEqual(mockItem);
});
it("does not filter out regions allowed by region policy", function () {
mockPolicyService.allow.andReturn(true);
controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.regions.length).toBe(2);
it("cleans up on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.calls[0].args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
it("Responds to status changes", function () {
mockPolicyService.allow.andReturn(true);
controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.regions.length).toBe(2);
expect(mockStatusCapability.listen).toHaveBeenCalled();
mockPolicyService.allow.andReturn(false);
mockStatusCapability.listen.mostRecentCall.args[0]();
expect(mockScope.regions.length).toBe(0);
});
it("Unregisters status listener", function () {
var mockListener = jasmine.createSpy('listener');
mockStatusCapability.listen.andReturn(mockListener);
controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
mockScope.$on.mostRecentCall.args[1]();
expect(mockListener).toHaveBeenCalled();
it("adds selection object to scope", function () {
expect(mockScope.selection).toEqual(selectable);
expect(controller.selectedItem()).toEqual(mockDomainObject);
});
});
}

View File

@@ -181,6 +181,8 @@ $colorPlotHash: $colorTick;
$stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$legendCollapsedNameMaxW: 50%;
$legendHoverValueBg: rgba($colorBodyFg, 0.1);
// Tree
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);

View File

@@ -181,6 +181,8 @@ $colorPlotHash: $colorTick;
$stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$legendCollapsedNameMaxW: 50%;
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
// Tree
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);

View File

@@ -1,54 +0,0 @@
define([
'text!./res/templates/autoflow-tabular.html',
'./src/AutoflowTabularController',
'./src/MCTAutoflowTable'
], function (
autoflowTabularTemplate,
AutoflowTabularController,
MCTAutoflowTable
) {
return function (options) {
return function (openmct) {
openmct.legacyRegistry.register("platform/features/autoflow", {
"name": "WARP Telemetry Adapter",
"description": "Retrieves telemetry from the WARP Server and provides related types and views.",
"resources": "res",
"extensions": {
"views": [
{
"key": "autoflow",
"name": "Autoflow Tabular",
"cssClass": "icon-packet",
"description": "A tabular view of packet contents.",
"template": autoflowTabularTemplate,
"type": options && options.type,
"needs": [
"telemetry"
],
"delegation": true
}
],
"controllers": [
{
"key": "AutoflowTabularController",
"implementation": AutoflowTabularController,
"depends": [
"$scope",
"$timeout",
"telemetrySubscriber"
]
}
],
"directives": [
{
"key": "mctAutoflowTable",
"implementation": MCTAutoflowTable
}
]
}
});
openmct.legacyRegistry.enable("platform/features/autoflow");
};
};
});

View File

@@ -1,26 +0,0 @@
<div class="items-holder abs contents autoflow obj-value-format"
ng-controller="AutoflowTabularController as autoflow">
<div class="abs l-flex-row holder t-autoflow-header l-autoflow-header">
<mct-include key="'input-filter'"
ng-model="autoflow.filter"
class="flex-elem">
</mct-include>
<div class="flex-elem grows t-last-update" title="Last Update">{{autoflow.updated()}}</div>
<a title="Change column width"
class="s-button flex-elem icon-arrows-right-left change-column-width"
ng-click="autoflow.increaseColumnWidth()"></a>
</div>
<div class="abs t-autoflow-items l-autoflow-items"
mct-resize="autoflow.setBounds(bounds)"
mct-resize-interval="50">
<mct-autoflow-table values="autoflow.rangeValues()"
objects="autoflow.getTelemetryObjects()"
rows="autoflow.getRows()"
classes="autoflow.classes()"
updated="autoflow.updated()"
column-width="autoflow.columnWidth()"
counter="autoflow.counter()"
>
</mct-autoflow-table>
</div>
</div>

View File

@@ -1,169 +0,0 @@
/*global angular*/
define(
[],
function () {
/**
* The link step for the `mct-autoflow-table` directive;
* watches scope and updates the DOM appropriately.
* See documentation in `MCTAutoflowTable.js` for the rationale
* for including this directive, as well as for an explanation
* of which values are placed in scope.
*
* @constructor
* @param {Scope} scope the scope for this usage of the directive
* @param element the jqLite-wrapped element which used this directive
*/
function AutoflowTableLinker(scope, element) {
var objects, // Domain objects at last structure refresh
rows, // Number of rows from last structure refresh
priorClasses = {},
valueSpans = {}; // Span elements to put data values in
// Create a new name-value pair in the specified column
function createListItem(domainObject, ul) {
// Create a new li, and spans to go in it.
var li = angular.element('<li>'),
titleSpan = angular.element('<span>'),
valueSpan = angular.element('<span>');
// Place spans in the li, and li into the column.
// valueSpan must precede titleSpan in the DOM due to new CSS float approach
li.append(valueSpan).append(titleSpan);
ul.append(li);
// Style appropriately
li.addClass('l-autoflow-row');
titleSpan.addClass('l-autoflow-item l');
valueSpan.addClass('l-autoflow-item r l-obj-val-format');
// Set text/tooltip for the name-value row
titleSpan.text(domainObject.getModel().name);
titleSpan.attr("title", domainObject.getModel().name);
// Keep a reference to the span which will hold the
// data value, to populate in the next refreshValues call
valueSpans[domainObject.getId()] = valueSpan;
return li;
}
// Create a new column of name-value pairs in this table.
function createColumn(el) {
// Create a ul
var ul = angular.element('<ul>');
// Add it into the mct-autoflow-table
el.append(ul);
// Style appropriately
ul.addClass('l-autoflow-col');
// Get the current col width and apply at time of column creation
// Important to do this here, as new columns could be created after
// the user has changed the width.
ul.css('width', scope.columnWidth + 'px');
// Return it, so some li elements can be added
return ul;
}
// Change the width of the columns when user clicks the resize button.
function resizeColumn() {
element.find('ul').css('width', scope.columnWidth + 'px');
}
// Rebuild the DOM associated with this table.
function rebuild(domainObjects, rowCount) {
var activeColumn;
// Empty out our cached span elements
valueSpans = {};
// Start with an empty DOM beneath this directive
element.html("");
// Add DOM elements for each domain object being displayed
// in this table.
domainObjects.forEach(function (object, index) {
// Start a new column if we'd run out of room
if (index % rowCount === 0) {
activeColumn = createColumn(element);
}
// Add the DOM elements for that object to whichever
// column (a `ul` element) is current.
createListItem(object, activeColumn);
});
}
// Update spans with values, as made available via the
// `values` attribute of this directive.
function refreshValues() {
// Get the available values
var values = scope.values || {},
classes = scope.classes || {};
// Populate all spans with those values (or clear
// those spans if no value is available)
(objects || []).forEach(function (object) {
var id = object.getId(),
span = valueSpans[id],
value;
if (span) {
// Look up the value...
value = values[id];
// ...and convert to empty string if it's undefined
value = value === undefined ? "" : value;
span.attr("data-value", value);
// Update the span
span.text(value);
span.attr("title", value);
span.removeClass(priorClasses[id]);
span.addClass(classes[id]);
priorClasses[id] = classes[id];
}
// Also need stale/alert/ok class
// on span
});
}
// Refresh the DOM for this table, if necessary
function refreshStructure() {
// Only rebuild if number of rows or set of objects
// has changed; otherwise, our structure is still valid.
if (scope.objects !== objects ||
scope.rows !== rows) {
// Track those values to support future refresh checks
objects = scope.objects;
rows = scope.rows;
// Rebuild the DOM
rebuild(objects || [], rows || 1);
// Refresh all data values shown
refreshValues();
}
}
// Changing the domain objects in use or the number
// of rows should trigger a structure change (DOM rebuild)
scope.$watch("objects", refreshStructure);
scope.$watch("rows", refreshStructure);
// When the current column width has been changed, resize the column
scope.$watch('columnWidth', resizeColumn);
// When the last-updated time ticks,
scope.$watch("updated", refreshValues);
// Update displayed values when the counter changes.
scope.$watch("counter", refreshValues);
}
return AutoflowTableLinker;
}
);

View File

@@ -1,324 +0,0 @@
define(
['moment'],
function (moment) {
var ROW_HEIGHT = 16,
SLIDER_HEIGHT = 10,
INITIAL_COLUMN_WIDTH = 225,
MAX_COLUMN_WIDTH = 525,
COLUMN_WIDTH_STEP = 25,
DEBOUNCE_INTERVAL = 100,
DATE_FORMAT = "YYYY-DDD HH:mm:ss.SSS\\Z",
NOT_UPDATED = "No updates",
EMPTY_ARRAY = [];
/**
* Responsible for supporting the autoflow tabular view.
* Implements the all-over logic which drives that view,
* mediating between template-provided areas, the included
* `mct-autoflow-table` directive, and the underlying
* domain object model.
* @constructor
*/
function AutflowTabularController(
$scope,
$timeout,
telemetrySubscriber
) {
var filterValue = "",
filterValueLowercase = "",
subscription,
filteredObjects = [],
lastUpdated = {},
updateText = NOT_UPDATED,
rangeValues = {},
classes = {},
limits = {},
updatePending = false,
lastBounce = Number.NEGATIVE_INFINITY,
columnWidth = INITIAL_COLUMN_WIDTH,
rows = 1,
counter = 0;
// Trigger an update of the displayed table by incrementing
// the counter that it watches.
function triggerDisplayUpdate() {
counter += 1;
}
// Check whether or not an object's name matches the
// user-entered filter value.
function filterObject(domainObject) {
return (domainObject.getModel().name || "")
.toLowerCase()
.indexOf(filterValueLowercase) !== -1;
}
// Comparator for sorting points back into packet order
function compareObject(objectA, objectB) {
var indexA = objectA.getModel().index || 0,
indexB = objectB.getModel().index || 0;
return indexA - indexB;
}
// Update the list of currently-displayed objects; these
// will be the subset of currently subscribed-to objects
// which match a user-entered filter.
function doUpdateFilteredObjects() {
// Generate the list
filteredObjects = (
subscription ?
subscription.getTelemetryObjects() :
[]
).filter(filterObject).sort(compareObject);
// Clear the pending flag
updatePending = false;
// Track when this occurred, so that we can wait
// a whole before updating again.
lastBounce = Date.now();
triggerDisplayUpdate();
}
// Request an update to the list of current objects; this may
// run on a timeout to avoid excessive calls, e.g. while the user
// is typing a filter.
function updateFilteredObjects() {
// Don't do anything if an update is already scheduled
if (!updatePending) {
if (Date.now() > lastBounce + DEBOUNCE_INTERVAL) {
// Update immediately if it's been long enough
doUpdateFilteredObjects();
} else {
// Otherwise, update later, and track that we have
// an update pending so that subsequent calls can
// be ignored.
updatePending = true;
$timeout(doUpdateFilteredObjects, DEBOUNCE_INTERVAL);
}
}
}
// Track the latest data values for this domain object
function recordData(telemetryObject) {
// Get latest domain/range values for this object.
var id = telemetryObject.getId(),
domainValue = subscription.getDomainValue(telemetryObject),
rangeValue = subscription.getRangeValue(telemetryObject);
// Track the most recent timestamp change observed...
if (domainValue !== undefined && domainValue !== lastUpdated[id]) {
lastUpdated[id] = domainValue;
// ... and update the displayable text for that timestamp
updateText = isNaN(domainValue) ? "" :
moment.utc(domainValue).format(DATE_FORMAT);
}
// Store data values into the rangeValues structure, which
// will be used to populate the table itself.
// Note that we want full precision here.
rangeValues[id] = rangeValue;
// Update limit states as well
classes[id] = limits[id] && (limits[id].evaluate({
// This relies on external knowledge that the
// range value of a telemetry point is encoded
// in its datum as "value."
value: rangeValue
}) || {}).cssClass;
}
// Look at telemetry objects from the subscription; this is watched
// to detect changes from the subscription.
function subscribedTelemetry() {
return subscription ?
subscription.getTelemetryObjects() : EMPTY_ARRAY;
}
// Update the data values which will be used to populate the table
function updateValues() {
subscribedTelemetry().forEach(recordData);
triggerDisplayUpdate();
}
// Getter-setter function for user-entered filter text.
function filter(value) {
// If value was specified, we're a setter
if (value !== undefined) {
// Store the new value
filterValue = value;
filterValueLowercase = value.toLowerCase();
// Change which objects appear in the table
updateFilteredObjects();
}
// Always act as a getter
return filterValue;
}
// Update the bounds (width and height) of this view;
// called from the mct-resize directive. Recalculates how
// many rows should appear in the contained table.
function setBounds(bounds) {
var availableSpace = bounds.height - SLIDER_HEIGHT;
rows = Math.max(1, Math.floor(availableSpace / ROW_HEIGHT));
}
// Increment the current column width, up to the defined maximum.
// When the max is hit, roll back to the default.
function increaseColumnWidth() {
columnWidth += COLUMN_WIDTH_STEP;
// Cycle down to the initial width instead of exceeding max
columnWidth = columnWidth > MAX_COLUMN_WIDTH ?
INITIAL_COLUMN_WIDTH : columnWidth;
}
// Get displayable text for last-updated value
function updated() {
return updateText;
}
// Unsubscribe, if a subscription is active.
function releaseSubscription() {
if (subscription) {
subscription.unsubscribe();
subscription = undefined;
}
}
// Update set of telemetry objects managed by this view
function updateTelemetryObjects(telemetryObjects) {
updateFilteredObjects();
limits = {};
telemetryObjects.forEach(function (telemetryObject) {
var id = telemetryObject.getId();
limits[id] = telemetryObject.getCapability('limit');
});
}
// Create a subscription for the represented domain object.
// This will resolve capability delegation as necessary.
function makeSubscription(domainObject) {
// Unsubscribe, if there is an existing subscription
releaseSubscription();
// Clear updated timestamp
lastUpdated = {};
updateText = NOT_UPDATED;
// Create a new subscription; telemetrySubscriber gets
// to do the meaningful work here.
subscription = domainObject && telemetrySubscriber.subscribe(
domainObject,
updateValues
);
// Our set of in-view telemetry objects may have changed,
// so update the set that is being passed down to the table.
updateFilteredObjects();
}
// Watch for changes to the set of objects which have telemetry
$scope.$watch(subscribedTelemetry, updateTelemetryObjects);
// Watch for the represented domainObject (this field will
// be populated by mct-representation)
$scope.$watch("domainObject", makeSubscription);
// Make sure we unsubscribe when this view is destroyed.
$scope.$on("$destroy", releaseSubscription);
return {
/**
* Get the number of rows which should be shown in this table.
* @return {number} the number of rows to show
*/
getRows: function () {
return rows;
},
/**
* Get the objects which should currently be displayed in
* this table. This will be watched, so the return value
* should be stable when this list is unchanging. Only
* objects which match the user-entered filter value should
* be returned here.
* @return {DomainObject[]} the domain objects to include in
* this table.
*/
getTelemetryObjects: function () {
return filteredObjects;
},
/**
* Set the bounds (width/height) of this autoflow tabular view.
* The template must ensure that these bounds are tracked on
* the table area only.
* @param bounds the bounds; and object with `width` and
* `height` properties, both as numbers, in pixels.
*/
setBounds: setBounds,
/**
* Increments the width of the autoflow column.
* Setting does not yet persist.
*/
increaseColumnWidth: increaseColumnWidth,
/**
* Get-or-set the user-supplied filter value.
* @param {string} [value] the new filter value; omit to use
* as a getter
* @returns {string} the user-supplied filter value
*/
filter: filter,
/**
* Get all range values for use in this table. These will be
* returned as an object of key-value pairs, where keys are
* domain object IDs, and values are the most recently observed
* data values associated with those objects, formatted for
* display.
* @returns {object.<string,string>} most recent values
*/
rangeValues: function () {
return rangeValues;
},
/**
* Get CSS classes to apply to specific rows, representing limit
* states and/or stale states. These are returned as key-value
* pairs where keys are domain object IDs, and values are CSS
* classes to display for domain objects with those IDs.
* @returns {object.<string,string>} CSS classes
*/
classes: function () {
return classes;
},
/**
* Get the "last updated" text for this view; this will be
* the most recent timestamp observed for any telemetry-
* providing object, formatted for display.
* @returns {string} the time of the most recent update
*/
updated: updated,
/**
* Get the current column width, in pixels.
* @returns {number} column width
*/
columnWidth: function () {
return columnWidth;
},
/**
* Keep a counter and increment this whenever the display
* should be updated; this will be watched by the
* `mct-autoflow-table`.
* @returns {number} a counter value
*/
counter: function () {
return counter;
}
};
}
return AutflowTabularController;
}
);

View File

@@ -1,60 +0,0 @@
define(
["./AutoflowTableLinker"],
function (AutoflowTableLinker) {
/**
* The `mct-autoflow-table` directive specifically supports
* autoflow tabular views; it is not intended for use outside
* of that view.
*
* This directive is responsible for creating the structure
* of the table in this view, and for updating its values.
* While this is achievable using a regular Angular template,
* this is undesirable from the perspective of performance
* due to the number of watches that can be involved for large
* tables. Instead, this directive will maintain a small number
* of watches, rebuilding table structure only when necessary,
* and updating displayed values in the more common case of
* new data arriving.
*
* @constructor
*/
function MCTAutoflowTable() {
return {
// Only applicable at the element level
restrict: "E",
// The link function; handles DOM update/manipulation
link: AutoflowTableLinker,
// Parameters to pass from attributes into scope
scope: {
// Set of domain objects to show in the table
objects: "=",
// Values for those objects, by ID
values: "=",
// CSS classes to show for objects, by ID
classes: "=",
// Number of rows to show before autoflowing
rows: "=",
// Time of last update; watched to refresh values
updated: "=",
// Current width of the autoflow column
columnWidth: "=",
// A counter used to trigger display updates
counter: "="
}
};
}
return MCTAutoflowTable;
}
);

View File

@@ -1,178 +0,0 @@
define(
["../src/AutoflowTableLinker"],
function (AutoflowTableLinker) {
describe("The mct-autoflow-table linker", function () {
var cachedAngular,
mockAngular,
mockScope,
mockElement,
mockElements,
linker;
// Utility function to generate more mock elements
function createMockElement(html) {
var mockEl = jasmine.createSpyObj(
"element-" + html,
[
"append",
"addClass",
"removeClass",
"text",
"attr",
"html",
"css",
"find"
]
);
mockEl.testHtml = html;
mockEl.append.andReturn(mockEl);
mockElements.push(mockEl);
return mockEl;
}
function createMockDomainObject(id) {
var mockDomainObject = jasmine.createSpyObj(
"domainObject-" + id,
["getId", "getModel"]
);
mockDomainObject.getId.andReturn(id);
mockDomainObject.getModel.andReturn({name: id.toUpperCase()});
return mockDomainObject;
}
function fireWatch(watchExpression, value) {
mockScope.$watch.calls.forEach(function (call) {
if (call.args[0] === watchExpression) {
call.args[1](value);
}
});
}
// AutoflowTableLinker accesses Angular in the global
// scope, since it is not injectable; we simulate that
// here by adding/removing it to/from the window object.
beforeEach(function () {
mockElements = [];
mockAngular = jasmine.createSpyObj("angular", ["element"]);
mockScope = jasmine.createSpyObj("scope", ["$watch"]);
mockElement = createMockElement('<div>');
mockAngular.element.andCallFake(createMockElement);
if (window.angular !== undefined) {
cachedAngular = window.angular;
}
window.angular = mockAngular;
linker = new AutoflowTableLinker(mockScope, mockElement);
});
afterEach(function () {
if (cachedAngular !== undefined) {
window.angular = cachedAngular;
} else {
delete window.angular;
}
});
it("watches for changes in inputs", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"objects",
jasmine.any(Function)
);
expect(mockScope.$watch).toHaveBeenCalledWith(
"rows",
jasmine.any(Function)
);
expect(mockScope.$watch).toHaveBeenCalledWith(
"counter",
jasmine.any(Function)
);
});
it("changes structure when domain objects change", function () {
// Set up scope
mockScope.rows = 4;
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
.map(createMockDomainObject);
// Fire an update to the set of objects
fireWatch("objects");
// Should have rebuilt with two columns of
// four and two rows each; first, by clearing...
expect(mockElement.html).toHaveBeenCalledWith("");
// Should have appended two columns...
expect(mockElement.append.calls.length).toEqual(2);
// ...which should have received two and four rows each
expect(mockElement.append.calls[0].args[0].append.calls.length)
.toEqual(4);
expect(mockElement.append.calls[1].args[0].append.calls.length)
.toEqual(2);
});
it("updates values", function () {
var mockSpans;
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
.map(createMockDomainObject);
mockScope.values = { a: 0 };
// Fire an update to the set of values
fireWatch("objects");
fireWatch("updated");
// Get all created spans
mockSpans = mockElements.filter(function (mockElem) {
return mockElem.testHtml === '<span>';
});
// First span should be a, should have gotten this value.
// This test detects, in particular, WTD-749
expect(mockSpans[0].text).toHaveBeenCalledWith('A');
expect(mockSpans[1].text).toHaveBeenCalledWith(0);
});
it("listens for changes in column width", function () {
var mockUL = createMockElement("<ul>");
mockElement.find.andReturn(mockUL);
mockScope.columnWidth = 200;
fireWatch("columnWidth", mockScope.columnWidth);
expect(mockUL.css).toHaveBeenCalledWith("width", "200px");
});
it("updates CSS classes", function () {
var mockSpans;
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
.map(createMockDomainObject);
mockScope.values = { a: "a value to find" };
mockScope.classes = { a: 'class-a' };
// Fire an update to the set of values
fireWatch("objects");
fireWatch("updated");
// Figure out which span holds the relevant value...
mockSpans = mockElements.filter(function (mockElem) {
return mockElem.testHtml === '<span>';
}).filter(function (mockSpan) {
var attrCalls = mockSpan.attr.calls;
return attrCalls.some(function (call) {
return call.args[0] === 'title' &&
call.args[1] === mockScope.values.a;
});
});
// ...and make sure it also has had its class applied
expect(mockSpans[0].addClass)
.toHaveBeenCalledWith(mockScope.classes.a);
});
});
}
);

View File

@@ -1,341 +0,0 @@
define(
["../src/AutoflowTabularController"],
function (AutoflowTabularController) {
describe("The autoflow tabular controller", function () {
var mockScope,
mockTimeout,
mockSubscriber,
mockDomainObject,
mockSubscription,
controller;
// Fire watches that are registered as functions.
function fireFnWatches() {
mockScope.$watch.calls.forEach(function (call) {
if (typeof call.args[0] === 'function') {
call.args[1](call.args[0]());
}
});
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
["$on", "$watch"]
);
mockTimeout = jasmine.createSpy("$timeout");
mockSubscriber = jasmine.createSpyObj(
"telemetrySubscriber",
["subscribe"]
);
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getId", "getModel", "getCapability"]
);
mockSubscription = jasmine.createSpyObj(
"subscription",
[
"unsubscribe",
"getTelemetryObjects",
"getDomainValue",
"getRangeValue"
]
);
mockSubscriber.subscribe.andReturn(mockSubscription);
mockDomainObject.getModel.andReturn({name: "something"});
controller = new AutoflowTabularController(
mockScope,
mockTimeout,
mockSubscriber
);
});
it("listens for the represented domain object", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"domainObject",
jasmine.any(Function)
);
});
it("provides a getter-setter function for filtering", function () {
expect(controller.filter()).toEqual("");
controller.filter("something");
expect(controller.filter()).toEqual("something");
});
it("tracks bounds and adjust number of rows accordingly", function () {
// Rows are 15px high, and need room for an 10px slider
controller.setBounds({ width: 700, height: 120 });
expect(controller.getRows()).toEqual(6); // 110 usable height / 16px
controller.setBounds({ width: 700, height: 240 });
expect(controller.getRows()).toEqual(14); // 230 usable height / 16px
});
it("subscribes to a represented object's telemetry", function () {
// Set up subscription, scope
mockSubscription.getTelemetryObjects
.andReturn([mockDomainObject]);
mockScope.domainObject = mockDomainObject;
// Invoke the watcher with represented domain object
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
// Should have subscribed to it
expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
mockDomainObject,
jasmine.any(Function)
);
// Should report objects as reported from subscription
expect(controller.getTelemetryObjects())
.toEqual([mockDomainObject]);
});
it("releases subscriptions on destroy", function () {
// Set up subscription...
mockSubscription.getTelemetryObjects
.andReturn([mockDomainObject]);
mockScope.domainObject = mockDomainObject;
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
// Verify precondition
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
// Make sure we're listening for $destroy
expect(mockScope.$on).toHaveBeenCalledWith(
"$destroy",
jasmine.any(Function)
);
// Fire a destroy event
mockScope.$on.mostRecentCall.args[1]();
// Should have unsubscribed
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
});
it("presents latest values and latest update state", function () {
// Make sure values are available
mockSubscription.getDomainValue.andReturn(402654321123);
mockSubscription.getRangeValue.andReturn(789);
mockDomainObject.getId.andReturn('testId');
// Set up subscription...
mockSubscription.getTelemetryObjects
.andReturn([mockDomainObject]);
mockScope.domainObject = mockDomainObject;
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
// Fire subscription callback
mockSubscriber.subscribe.mostRecentCall.args[1]();
// ...and exposed the results for template to consume
expect(controller.updated()).toEqual("1982-278 08:25:21.123Z");
expect(controller.rangeValues().testId).toEqual(789);
});
it("sorts domain objects by index", function () {
var testIndexes = { a: 2, b: 1, c: 3, d: 0 },
mockDomainObjects = Object.keys(testIndexes).sort().map(function (id) {
var mockDomainObj = jasmine.createSpyObj(
"domainObject",
["getId", "getModel"]
);
mockDomainObj.getId.andReturn(id);
mockDomainObj.getModel.andReturn({ index: testIndexes[id] });
return mockDomainObj;
});
// Expose those domain objects...
mockSubscription.getTelemetryObjects.andReturn(mockDomainObjects);
mockScope.domainObject = mockDomainObject;
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
// Fire subscription callback
mockSubscriber.subscribe.mostRecentCall.args[1]();
// Controller should expose same objects, but sorted by index from model
expect(controller.getTelemetryObjects()).toEqual([
mockDomainObjects[3], // d, index=0
mockDomainObjects[1], // b, index=1
mockDomainObjects[0], // a, index=2
mockDomainObjects[2] // c, index=3
]);
});
it("uses a timeout to throttle update", function () {
// Set up subscription...
mockSubscription.getTelemetryObjects
.andReturn([mockDomainObject]);
mockScope.domainObject = mockDomainObject;
// Set the object in view; should not need a timeout
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
expect(mockTimeout.calls.length).toEqual(0);
// Next call should schedule an update on a timeout
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
expect(mockTimeout.calls.length).toEqual(1);
// ...but this last one should not, since existing
// timeout will cover it
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
expect(mockTimeout.calls.length).toEqual(1);
});
it("allows changing column width", function () {
var initialWidth = controller.columnWidth();
controller.increaseColumnWidth();
expect(controller.columnWidth()).toBeGreaterThan(initialWidth);
});
describe("filter", function () {
var doFilter,
filteredObjects,
filteredObjectNames;
beforeEach(function () {
var telemetryObjects,
updateFilteredObjects;
telemetryObjects = [
'DEF123',
'abc789',
'456abc',
'4ab3cdef',
'hjs[12].*(){}^\\'
].map(function (objectName, index) {
var mockTelemetryObject = jasmine.createSpyObj(
objectName,
["getId", "getModel"]
);
mockTelemetryObject.getId.andReturn(objectName);
mockTelemetryObject.getModel.andReturn({
name: objectName,
index: index
});
return mockTelemetryObject;
});
mockSubscription
.getTelemetryObjects
.andReturn(telemetryObjects);
// Trigger domainObject change to create subscription.
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
updateFilteredObjects = function () {
filteredObjects = controller.getTelemetryObjects();
filteredObjectNames = filteredObjects.map(function (o) {
return o.getModel().name;
});
};
doFilter = function (term) {
controller.filter(term);
// Filter is debounced so we have to force it to occur.
mockTimeout.mostRecentCall.args[0]();
updateFilteredObjects();
};
updateFilteredObjects();
});
it("initially shows all objects", function () {
expect(filteredObjectNames).toEqual([
'DEF123',
'abc789',
'456abc',
'4ab3cdef',
'hjs[12].*(){}^\\'
]);
});
it("by blank string matches all objects", function () {
doFilter('');
expect(filteredObjectNames).toEqual([
'DEF123',
'abc789',
'456abc',
'4ab3cdef',
'hjs[12].*(){}^\\'
]);
});
it("exactly matches an object name", function () {
doFilter('4ab3cdef');
expect(filteredObjectNames).toEqual(['4ab3cdef']);
});
it("partially matches object names", function () {
doFilter('abc');
expect(filteredObjectNames).toEqual([
'abc789',
'456abc'
]);
});
it("matches case insensitive names", function () {
doFilter('def');
expect(filteredObjectNames).toEqual([
'DEF123',
'4ab3cdef'
]);
});
it("works as expected with special characters", function () {
doFilter('[12]');
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
doFilter('.*');
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
doFilter('.*()');
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
doFilter('.*?');
expect(filteredObjectNames).toEqual([]);
doFilter('.+');
expect(filteredObjectNames).toEqual([]);
});
it("exposes CSS classes from limits", function () {
var id = mockDomainObject.getId(),
testClass = "some-css-class",
mockLimitCapability =
jasmine.createSpyObj('limit', ['evaluate']);
mockDomainObject.getCapability.andCallFake(function (key) {
return key === 'limit' && mockLimitCapability;
});
mockLimitCapability.evaluate
.andReturn({ cssClass: testClass });
mockSubscription.getTelemetryObjects
.andReturn([mockDomainObject]);
fireFnWatches();
mockSubscriber.subscribe.mostRecentCall.args[1]();
expect(controller.classes()[id]).toEqual(testClass);
});
it("exposes a counter that changes with each update", function () {
var i, prior;
for (i = 0; i < 10; i += 1) {
prior = controller.counter();
expect(controller.counter()).toEqual(prior);
mockSubscriber.subscribe.mostRecentCall.args[1]();
expect(controller.counter()).not.toEqual(prior);
}
});
});
});
}
);

View File

@@ -1,39 +0,0 @@
define(
["../src/MCTAutoflowTable"],
function (MCTAutoflowTable) {
describe("The mct-autoflow-table directive", function () {
var mctAutoflowTable;
beforeEach(function () {
mctAutoflowTable = new MCTAutoflowTable();
});
// Real functionality is contained/tested in the linker,
// so just check to make sure we're exposing the directive
// appropriately.
it("is applicable at the element level", function () {
expect(mctAutoflowTable.restrict).toEqual("E");
});
it("two-ways binds needed scope variables", function () {
expect(mctAutoflowTable.scope).toEqual({
objects: "=",
values: "=",
rows: "=",
updated: "=",
classes: "=",
columnWidth: "=",
counter: "="
});
});
it("provides a link function", function () {
expect(mctAutoflowTable.link).toEqual(jasmine.any(Function));
});
});
}
);

View File

@@ -45,7 +45,7 @@ define(
FollowIndicator.prototype.getText = function () {
var timer = this.timerService.getTimer();
return (timer) ? 'Following timer ' + timer.getModel().name : NO_TIMER;
return timer ? ('Following timer ' + timer.name) : NO_TIMER;
};
FollowIndicator.prototype.getDescription = function () {

View File

@@ -42,18 +42,15 @@ define(["../../src/indicators/FollowIndicator"], function (FollowIndicator) {
});
describe("when a timer is set", function () {
var testModel;
var mockDomainObject;
var testObject;
beforeEach(function () {
testModel = { name: "some timer!" };
mockDomainObject = jasmine.createSpyObj('timer', ['getModel']);
mockDomainObject.getModel.andReturn(testModel);
mockTimerService.getTimer.andReturn(mockDomainObject);
testObject = { name: "some timer!" };
mockTimerService.getTimer.andReturn(testObject);
});
it("displays the timer's name", function () {
expect(indicator.getText().indexOf(testModel.name))
expect(indicator.getText().indexOf(testObject.name))
.not.toEqual(-1);
});
});

View File

@@ -260,7 +260,9 @@ define([
"key": "LayoutController",
"implementation": LayoutController,
"depends": [
"$scope"
"$scope",
"$element",
"openmct"
]
},
{
@@ -270,7 +272,8 @@ define([
"$scope",
"$q",
"dialogService",
"openmct"
"openmct",
"$element"
]
}
],
@@ -334,46 +337,6 @@ define([
"conversion": "number[]"
}
]
},
{
"key": "telemetry.panel",
"name": "Telemetry Panel",
"cssClass": "icon-telemetry-panel",
"description": "A panel for collecting telemetry elements.",
"priority": 899,
"delegates": [
"telemetry"
],
"features": "creation",
"contains": [
{
"has": "telemetry"
}
],
"model": {
"composition": []
},
"properties": [
{
"name": "Layout Grid",
"control": "composite",
"items": [
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
}
],
"pattern": "^(\\d*[1-9]\\d*)?$",
"property": "layoutGrid",
"conversion": "number[]"
}
]
}
]
}

View File

@@ -23,7 +23,7 @@
ng-controller="FixedController as controller">
<!-- Background grid -->
<div class="l-grid-holder" ng-click="controller.clearSelection()">
<div class="l-grid-holder" ng-click="controller.bypassSelection($event)">
<div class="l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
@@ -35,33 +35,28 @@
<!-- Fixed position elements -->
<div ng-repeat="element in controller.getElements()"
class="l-fixed-position-item s-selectable s-moveable s-hover-border"
ng-class="{
's-not-selected': controller.selected() && !controller.selected(element),
's-selected': controller.selected(element)
}"
ng-style="element.style"
ng-click="controller.select(element)">
mct-selectable="controller.getContext(element)"
mct-init-select="controller.shouldSelect(element)">
<mct-include key="element.template"
parameters="{ gridSize: controller.getGridSize() }"
ng-model="element">
</mct-include>
</mct-include>
</div>
<!-- Selection highlight, handles -->
<span class="s-selected s-moveable" ng-if="controller.selected()">
<span class="s-selected s-moveable" ng-if="controller.isElementSelected()">
<div class="l-fixed-position-item t-edit-handle-holder"
mct-drag-down="controller.moveHandle().startDrag(controller.selected())"
mct-drag-down="controller.moveHandle().startDrag()"
mct-drag="controller.moveHandle().continueDrag(delta)"
mct-drag-up="controller.moveHandle().endDrag()"
ng-style="controller.selected().style">
mct-drag-up="controller.endDrag()"
ng-style="controller.getSelectedElementStyle()">
</div>
<div ng-repeat="handle in controller.handles()"
class="l-fixed-position-item-handle edit-corner"
ng-style="handle.style()"
mct-drag-down="handle.startDrag()"
mct-drag="handle.continueDrag(delta)"
mct-drag-up="handle.endDrag()">
mct-drag-up="controller.endDrag(handle)">
</div>
</span>
</div>

View File

@@ -22,10 +22,12 @@
<div class="abs l-layout"
ng-controller="LayoutController as controller"
ng-click="controller.clearSelection()">
ng-click="controller.bypassSelection($event)">
<!-- Background grid -->
<div class="l-grid-holder" ng-click="controller.clearSelection()">
<div class="l-grid-holder"
ng-show="!controller.drilledIn"
ng-click="controller.bypassSelection($event)">
<div class="l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
@@ -34,10 +36,13 @@
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
</div>
<div class='abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border'
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-selected':controller.selected(childObject) }"
<div class="abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}"
data-layout-id="{{childObject.getId() + '-' + $id}}"
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
ng-repeat="childObject in composition"
ng-click="controller.select($event, childObject.getId())"
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
mct-selectable="controller.getContext(childObject, true)"
ng-dblclick="controller.drill($event, childObject)"
ng-style="controller.getFrameStyle(childObject.getId())">
<mct-representation key="'frame'"
@@ -45,7 +50,7 @@
mct-object="childObject">
</mct-representation>
<!-- Drag handles -->
<span class="abs t-edit-handle-holder s-hover-border" ng-if="controller.selected(childObject)">
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
<span class="edit-handle edit-move"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
mct-drag="controller.continueDrag(delta)"
@@ -73,7 +78,6 @@
mct-drag-up="controller.endDrag()">
</span>
</span>
</div>
</div>

View File

@@ -47,7 +47,7 @@ define(
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function FixedController($scope, $q, dialogService, openmct) {
function FixedController($scope, $q, dialogService, openmct, $element) {
this.names = {}; // Cache names by ID
this.values = {}; // Cache values by ID
this.elementProxiesById = {};
@@ -55,9 +55,11 @@ define(
this.telemetryObjects = [];
this.subscriptions = [];
this.openmct = openmct;
this.$element = $element;
this.$scope = $scope;
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
this.fixedViewSelectable = false;
var self = this;
[
@@ -87,9 +89,8 @@ define(
// Update the style for a selected element
function updateSelectionStyle() {
var element = self.selection && self.selection.get();
if (element) {
element.style = convertPosition(element);
if (self.selectedElementProxy) {
self.selectedElementProxy.style = convertPosition(self.selectedElementProxy);
}
}
@@ -136,25 +137,19 @@ define(
// Decorate elements in the current configuration
function refreshElements() {
// Cache selection; we are instantiating new proxies
// so we may want to restore this.
var selected = self.selection && self.selection.get(),
elements = (($scope.configuration || {}).elements || []),
index = -1; // Start with a 'not-found' value
// Find the selection in the new array
if (selected !== undefined) {
index = elements.indexOf(selected.element);
}
var elements = (($scope.configuration || {}).elements || []);
// Create the new proxies...
self.elementProxies = elements.map(makeProxyElement);
// Clear old selection, and restore if appropriate
if (self.selection) {
self.selection.deselect();
if (index > -1) {
self.select(self.elementProxies[index]);
// If selection is not in array, select parent.
// Otherwise, set the element to select after refresh.
if (self.selectedElementProxy) {
var index = elements.indexOf(self.selectedElementProxy.element);
if (index === -1) {
self.$element[0].click();
} else if (!self.elementToSelectAfterRefresh) {
self.elementToSelectAfterRefresh = self.elementProxies[index].element;
}
}
@@ -224,12 +219,12 @@ define(
$scope.configuration.elements || [];
// Store the position of this element.
$scope.configuration.elements.push(element);
self.elementToSelectAfterRefresh = element;
// Refresh displayed elements
refreshElements();
// Select the newly-added element
self.select(
self.elementProxies[self.elementProxies.length - 1]
);
// Mark change as persistable
if ($scope.commit) {
$scope.commit("Dropped an element.");
@@ -263,21 +258,36 @@ define(
self.getTelemetry($scope.domainObject);
}
// Sets the selectable object in response to the selection change event.
function setSelection(selectable) {
var selection = selectable[0];
if (!selection) {
return;
}
if (selection.context.elementProxy) {
self.selectedElementProxy = selection.context.elementProxy;
self.mvHandle = self.generateDragHandle(self.selectedElementProxy);
self.resizeHandles = self.generateDragHandles(self.selectedElementProxy);
} else {
// Make fixed view selectable if it's not already.
if (!self.fixedViewSelectable && selectable.length === 1) {
self.fixedViewSelectable = true;
selection.context.viewProxy = new FixedProxy(addElement, $q, dialogService);
self.openmct.selection.select(selection);
}
self.resizeHandles = [];
self.mvHandle = undefined;
self.selectedElementProxy = undefined;
}
}
this.elementProxies = [];
this.generateDragHandle = generateDragHandle;
this.generateDragHandles = generateDragHandles;
// Track current selection state
$scope.$watch("selection", function (selection) {
this.selection = selection;
// Expose the view's selection proxy
if (this.selection) {
this.selection.proxy(
new FixedProxy(addElement, $q, dialogService)
);
}
}.bind(this));
this.updateSelectionStyle = updateSelectionStyle;
// Detect changes to grid size
$scope.$watch("model.layoutGrid", updateElementPositions);
@@ -298,10 +308,13 @@ define(
$scope.$on("$destroy", function () {
self.unsubscribe();
self.openmct.time.off("bounds", updateDisplayBounds);
self.openmct.selection.off("change", setSelection);
});
// Respond to external bounds changes
this.openmct.time.on("bounds", updateDisplayBounds);
this.openmct.selection.on('change', setSelection);
this.$element.on('click', this.bypassSelection.bind(this));
}
/**
@@ -360,10 +373,10 @@ define(
*/
FixedController.prototype.updateView = function (telemetryObject, datum) {
var metadata = this.openmct.telemetry.getMetadata(telemetryObject);
var telemetryKeyToDisplay = this.chooseTelemetryKeyToDisplay(metadata);
var formattedTelemetryValue = this.getFormattedTelemetryValueForKey(telemetryKeyToDisplay, datum, metadata);
var valueMetadata = this.chooseValueMetadataToDisplay(metadata);
var formattedTelemetryValue = this.getFormattedTelemetryValueForKey(valueMetadata, datum);
var limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, telemetryKeyToDisplay);
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, valueMetadata);
this.setDisplayedValue(
telemetryObject,
@@ -376,29 +389,28 @@ define(
/**
* @private
*/
FixedController.prototype.getFormattedTelemetryValueForKey = function (telemetryKeyToDisplay, datum, metadata) {
var valueMetadata = metadata.value(telemetryKeyToDisplay);
FixedController.prototype.getFormattedTelemetryValueForKey = function (valueMetadata, datum) {
var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
return formatter.format(datum[valueMetadata.key]);
return formatter.format(datum);
};
/**
* @private
*/
FixedController.prototype.chooseTelemetryKeyToDisplay = function (metadata) {
FixedController.prototype.chooseValueMetadataToDisplay = function (metadata) {
// If there is a range value, show that preferentially
var telemetryKeyToDisplay = metadata.valuesForHints(['range'])[0];
var valueMetadata = metadata.valuesForHints(['range'])[0];
// If no range is defined, default to the highest priority non time-domain data.
if (telemetryKeyToDisplay === undefined) {
if (valueMetadata === undefined) {
var valuesOrderedByPriority = metadata.values();
telemetryKeyToDisplay = valuesOrderedByPriority.filter(function (valueMetadata) {
return !(valueMetadata.hints.domain);
valueMetadata = valuesOrderedByPriority.filter(function (values) {
return !(values.hints.domain);
})[0];
}
return telemetryKeyToDisplay.source;
return valueMetadata;
};
/**
@@ -492,38 +504,56 @@ define(
};
/**
* Check if the element is currently selected, or (if no
* argument is supplied) get the currently selected element.
* @returns {boolean} true if selected
* Checks if the element should be selected or not.
*
* @param elementProxy the element to check
* @returns {boolean} true if the element should be selected.
*/
FixedController.prototype.selected = function (element) {
var selection = this.selection;
return selection && ((arguments.length > 0) ?
selection.selected(element) : selection.get());
};
/**
* Set the active user selection in this view.
* @param element the element to select
*/
FixedController.prototype.select = function select(element) {
if (this.selection) {
// Update selection...
this.selection.select(element);
// ...as well as move, resize handles
this.mvHandle = this.generateDragHandle(element);
this.resizeHandles = this.generateDragHandles(element);
FixedController.prototype.shouldSelect = function (elementProxy) {
if (elementProxy.element === this.elementToSelectAfterRefresh) {
delete this.elementToSelectAfterRefresh;
return true;
} else {
return false;
}
};
/**
* Clear the current user selection.
* Checks if an element is currently selected.
*
* @returns {boolean} true if an element is selected.
*/
FixedController.prototype.clearSelection = function () {
if (this.selection) {
this.selection.deselect();
this.resizeHandles = [];
this.mvHandle = undefined;
FixedController.prototype.isElementSelected = function () {
return (this.selectedElementProxy) ? true : false;
};
/**
* Gets the style for the selected element.
*
* @returns {string} element style
*/
FixedController.prototype.getSelectedElementStyle = function () {
return (this.selectedElementProxy) ? this.selectedElementProxy.style : undefined;
};
/**
* Gets the selected element.
*
* @returns the selected element
*/
FixedController.prototype.getSelectedElement = function () {
return this.selectedElementProxy;
};
/**
* Prevents the event from bubbling up if drag is in progress.
*/
FixedController.prototype.bypassSelection = function ($event) {
if (this.dragInProgress) {
if ($event) {
$event.stopPropagation();
}
return;
}
};
@@ -544,6 +574,38 @@ define(
return this.mvHandle;
};
/**
* Gets the selection context.
*
* @param elementProxy the element proxy
* @returns {object} the context object which includes elementProxy and toolbar
*/
FixedController.prototype.getContext = function (elementProxy) {
return {
elementProxy: elementProxy,
toolbar: elementProxy
};
};
/**
* End drag.
*
* @param handle the resize handle
*/
FixedController.prototype.endDrag = function (handle) {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
if (handle) {
handle.endDrag();
} else {
this.moveHandle().endDrag();
}
};
return FixedController;
}
);

View File

@@ -65,7 +65,7 @@ define(
* Start a drag gesture. This should be called when a drag
* begins to track initial state.
*/
FixedDragHandle.prototype.startDrag = function startDrag() {
FixedDragHandle.prototype.startDrag = function () {
// Cache initial x/y positions
this.dragging = {
x: this.elementHandle.x(),

View File

@@ -27,9 +27,11 @@
*/
define(
[
'zepto',
'./LayoutDrag'
],
function (
$,
LayoutDrag
) {
@@ -50,10 +52,12 @@ define(
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function LayoutController($scope) {
function LayoutController($scope, $element, openmct) {
var self = this,
callbackCount = 0;
this.$element = $element;
// Update grid size when it changed
function updateGridSize(layoutGrid) {
var oldSize = self.gridSize;
@@ -123,12 +127,11 @@ define(
self.layoutPanels(ids);
self.setFrames(ids);
// If there is a newly-dropped object, select it.
if (self.droppedIdToSelectAfterRefresh) {
self.select(null, self.droppedIdToSelectAfterRefresh);
delete self.droppedIdToSelectAfterRefresh;
} else if (composition.indexOf(self.selectedId) === -1) {
self.clearSelection();
if (self.selectedId &&
self.selectedId !== $scope.domainObject.getId() &&
composition.indexOf(self.selectedId) === -1) {
// Click triggers selection of layout parent.
self.$element[0].click();
}
}
});
@@ -160,22 +163,39 @@ define(
}
};
// Sets the selectable object in response to the selection change event.
function setSelection(selectable) {
var selection = selectable[0];
if (!selection) {
delete self.selectedId;
return;
}
self.selectedId = selection.context.oldItem.getId();
self.drilledIn = undefined;
self.selectable = selectable;
}
this.positions = {};
this.rawPositions = {};
this.gridSize = DEFAULT_GRID_SIZE;
this.$scope = $scope;
this.drilledIn = undefined;
this.openmct = openmct;
// Watch for changes to the grid size in the model
$scope.$watch("model.layoutGrid", updateGridSize);
$scope.$watch("selection", function (selection) {
this.selection = selection;
}.bind(this));
// Update composed objects on screen, and position panes
$scope.$watchCollection("model.composition", refreshComposition);
// Position panes where they are dropped
openmct.selection.on('change', setSelection);
$scope.$on("$destroy", function () {
openmct.selection.off("change", setSelection);
});
$scope.$on("mctDrop", handleDrop);
}
@@ -357,37 +377,14 @@ define(
};
/**
* Check if the object is currently selected.
* Checks if the object is currently selected.
*
* @param {string} obj the object to check for selection
* @returns {boolean} true if selected, otherwise false
*/
LayoutController.prototype.selected = function (obj) {
return !!this.selectedId && this.selectedId === obj.getId();
};
/**
* Set the active user selection in this view.
*
* @param event the mouse event
* @param {string} id the object id
*/
LayoutController.prototype.select = function (event, id) {
if (event) {
event.stopPropagation();
if (this.selection) {
event.preventDefault();
}
}
this.selectedId = id;
var selectedObj = {};
selectedObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id);
if (this.selection) {
this.selection.select(selectedObj);
}
var sobj = this.openmct.selection.get()[0];
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
};
/**
@@ -396,7 +393,7 @@ define(
* @param {string} id the object id
* @private
*/
LayoutController.prototype.toggleFrame = function (id) {
LayoutController.prototype.toggleFrame = function (id, domainObject) {
var configuration = this.$scope.configuration;
if (!configuration.panels[id]) {
@@ -404,21 +401,75 @@ define(
}
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
this.select(undefined, id); // reselect so toolbar updates
var selection = this.openmct.selection.get();
selection[0].context.toolbar = this.getToolbar(id, domainObject);
this.openmct.selection.select(selection); // reselect so toolbar updates
};
/**
* Clear the current user selection.
* Gets the toolbar object for the given domain object.
*
* @param id the domain object id
* @param domainObject the domain object
* @returns {object}
* @private
*/
LayoutController.prototype.clearSelection = function () {
LayoutController.prototype.getToolbar = function (id, domainObject) {
var toolbarObj = {};
toolbarObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id, domainObject);
return toolbarObj;
};
/**
* Bypasses selection if drag is in progress.
*
* @param event the angular event object
*/
LayoutController.prototype.bypassSelection = function (event) {
if (this.dragInProgress) {
if (event) {
event.stopPropagation();
}
return;
}
};
/**
* Checks if the domain object is drilled in.
*
* @param domainObject the domain object
* @return true if the object is drilled in, false otherwise
*/
LayoutController.prototype.isDrilledIn = function (domainObject) {
return this.drilledIn === domainObject.getId();
};
/**
* Puts the given object in the drilled-in mode.
*
* @param event the angular event object
* @param domainObject the domain object
*/
LayoutController.prototype.drill = function (event, domainObject) {
if (event) {
event.stopPropagation();
}
if (!domainObject.getCapability('editor').inEditContext()) {
return;
}
if (this.selection) {
this.selection.deselect();
delete this.selectedId;
if (!domainObject.hasCapability('composition')) {
return;
}
// Disable since fixed position doesn't use the selection API yet
if (domainObject.getModel().type === 'telemetry.fixed') {
return;
}
this.drilledIn = domainObject.getId();
};
/**
@@ -440,6 +491,36 @@ define(
return this.gridSize;
};
/**
* Gets the selection context.
*
* @param domainObject the domain object
* @returns {object} the context object which includes
* item, oldItem and toolbar
*/
LayoutController.prototype.getContext = function (domainObject, toolbar) {
return {
item: domainObject.useCapability('adapter'),
oldItem: domainObject,
toolbar: toolbar ? this.getToolbar(domainObject.getId(), domainObject) : undefined
};
};
/**
* Selects a newly-dropped object.
*
* @param classSelector the css class selector
* @param domainObject the domain object
*/
LayoutController.prototype.selectIfNew = function (selector, domainObject) {
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
setTimeout(function () {
$('[data-layout-id="' + selector + '"]')[0].click();
delete this.droppedIdToSelectAfterRefresh;
}.bind(this), 0);
}
};
return LayoutController;
}
);

View File

@@ -21,23 +21,12 @@
*****************************************************************************/
define([
'zepto'
'zepto',
'../../../commonUI/general/src/services/Overlay'
], function (
$
$,
Overlay
) {
var OVERLAY_TEMPLATE = '' +
' <div class="abs blocker"></div>' +
' <div class="abs outer-holder">' +
' <a class="close icon-x-in-circle"></a>' +
' <div class="abs inner-holder l-flex-col">' +
' <div class="t-contents flex-elem holder grows"></div>' +
' <div class="bottom-bar flex-elem holder">' +
' <a class="t-done s-button major">Done</a>' +
' </div>' +
' </div>' +
' </div>';
/**
* MCT Trigger Modal is intended for use in only one location: inside the
* object-header to allow views in a layout to be popped out in a modal.
@@ -50,9 +39,11 @@ define([
* descendent of a `.frame` element.
*/
function MCTTriggerModal($document) {
var document = $document[0];
function link($scope, $element) {
var actions = $scope.domainObject.getCapability('action'),
notebookAction = actions.getActions({key: 'notebook-new-entry'})[0];
var frame = $element.parent();
for (var i = 0; i < 10; i++) {
@@ -67,61 +58,39 @@ define([
}
frame = frame[0];
var layoutContainer = frame.parentElement,
isOpen = false,
toggleOverlay,
overlay,
closeButton,
doneButton,
blocker,
overlayContainer;
function openOverlay() {
// Remove frame classes from being applied in a non-frame context
$(frame).removeClass('frame frame-template');
overlay = document.createElement('div');
$(overlay).addClass('abs overlay l-large-view');
overlay.innerHTML = OVERLAY_TEMPLATE;
overlayContainer = overlay.querySelector('.t-contents');
closeButton = overlay.querySelector('a.close');
closeButton.addEventListener('click', toggleOverlay);
doneButton = overlay.querySelector('a.t-done');
doneButton.addEventListener('click', toggleOverlay);
blocker = overlay.querySelector('.abs.blocker');
blocker.addEventListener('click', toggleOverlay);
document.body.appendChild(overlay);
layoutContainer.removeChild(frame);
overlayContainer.appendChild(frame);
}
var layoutContainer = frame.parentElement;
function closeOverlay() {
$(frame).addClass('frame frame-template');
overlayContainer.removeChild(frame);
layoutContainer.appendChild(frame);
document.body.removeChild(overlay);
closeButton.removeEventListener('click', toggleOverlay);
closeButton = undefined;
doneButton.removeEventListener('click', toggleOverlay);
doneButton = undefined;
blocker.removeEventListener('click', toggleOverlay);
blocker = undefined;
overlayContainer = undefined;
overlay = undefined;
}
var notebookButton = notebookAction ?
[
{
class: 'icon-notebook new-notebook-entry',
title: 'New Notebook Entry',
clickHandler: function (event) {
event.stopPropagation();
notebookAction.perform();
}
}
] : [];
toggleOverlay = function () {
if (!isOpen) {
openOverlay();
isOpen = true;
} else {
closeOverlay();
isOpen = false;
}
};
var overlayService = new Overlay ({
$document: $document,
$scope: $scope,
$element: frame,
overlayWillMount: function () {
$(frame).removeClass('frame frame-template');
layoutContainer.removeChild(frame);
},
overlayDidUnmount: function () {
$(frame).addClass('frame frame-template');
layoutContainer.appendChild(frame);
},
browseBarButtons: notebookButton
});
$element.on('click', toggleOverlay);
$element.on('click', overlayService.toggleOverlay);
$scope.$on('$destroy', function () {
$element.off('click', toggleOverlay);
$element.off('click', overlayService.toggleOverlay);
});
}

View File

@@ -55,8 +55,8 @@ define(
* @param element the fixed position element, as stored in its
* configuration
* @param index the element's index within its array
* @param {number[]} gridSize the current layout grid size in [x,y] from
* @param {Array} elements the full array of elements
* @param {number[]} gridSize the current layout grid size in [x,y] from
*/
function ElementProxy(element, index, elements, gridSize) {
/**

View File

@@ -21,8 +21,14 @@
*****************************************************************************/
define(
["../src/FixedController"],
function (FixedController) {
[
"../src/FixedController",
"zepto"
],
function (
FixedController,
$
) {
describe("The Fixed Position controller", function () {
var mockScope,
@@ -46,6 +52,9 @@ define(
mockMetadata,
mockTimeSystem,
mockLimitEvaluator,
mockSelection,
$element = [],
selectable = [],
controller;
// Utility function; find a watch for a given expression
@@ -97,8 +106,8 @@ define(
'telemetryFormatter',
['format']
);
mockFormatter.format.andCallFake(function (value) {
return "Formatted " + value;
mockFormatter.format.andCallFake(function (valueMetadata) {
return "Formatted " + valueMetadata.value;
});
mockDomainObject = jasmine.createSpyObj(
@@ -180,17 +189,30 @@ define(
mockScope.model = testModel;
mockScope.configuration = testConfiguration;
mockScope.selection = jasmine.createSpyObj(
'selection',
['select', 'get', 'selected', 'deselect', 'proxy']
);
selectable[0] = {
context: {
oldItem: mockDomainObject
}
};
mockSelection = jasmine.createSpyObj("selection", [
'select',
'on',
'off',
'get'
]);
mockSelection.get.andCallThrough();
mockOpenMCT = {
time: mockConductor,
telemetry: mockTelemetryAPI,
composition: mockCompositionAPI
composition: mockCompositionAPI,
selection: mockSelection
};
$element = $('<div></div>');
spyOn($element[0], 'click');
mockMetadata = jasmine.createSpyObj('mockMetadata', [
'valuesForHints',
'value',
@@ -226,11 +248,11 @@ define(
mockScope,
mockQ,
mockDialogService,
mockOpenMCT
mockOpenMCT,
$element
);
findWatch("model.layoutGrid")(testModel.layoutGrid);
findWatch("selection")(mockScope.selection);
});
it("subscribes when a domain object is available", function () {
@@ -306,41 +328,41 @@ define(
});
it("allows elements to be selected", function () {
var elements;
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
controller.select(elements[1]);
expect(mockScope.selection.select)
.toHaveBeenCalledWith(elements[1]);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.isElementSelected()).toBe(true);
});
it("allows selection retrieval", function () {
// selected with no arguments should give the current
// selection
var elements;
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
controller.select(elements[1]);
mockScope.selection.get.andReturn(elements[1]);
expect(controller.selected()).toEqual(elements[1]);
selectable[0].context.elementProxy = elements[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.getSelectedElement()).toEqual(elements[1]);
});
it("allows selections to be cleared", function () {
var elements;
it("selects the parent view when selected element is removed", function () {
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
controller.select(elements[1]);
controller.clearSelection();
expect(controller.selected(elements[1])).toBeFalsy();
var elements = controller.getElements();
selectable[0].context.elementProxy = elements[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
elements[1].remove();
testModel.modified = 2;
findWatch("model.modified")(testModel.modified);
expect($element[0].click).toHaveBeenCalled();
});
it("retains selections during refresh", function () {
@@ -352,23 +374,21 @@ define(
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
controller.select(elements[1]);
selectable[0].context.elementProxy = elements[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
// Verify precondition
expect(mockScope.selection.select.calls.length).toEqual(1);
// Mimic selection behavior
mockScope.selection.get.andReturn(elements[1]);
expect(controller.getSelectedElement()).toEqual(elements[1]);
elements[2].remove();
testModel.modified = 2;
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
// Verify removal, as test assumes this
expect(elements.length).toEqual(2);
expect(mockScope.selection.select.calls.length).toEqual(2);
expect(controller.shouldSelect(elements[1])).toBe(true);
});
it("Displays received values for telemetry elements", function () {
@@ -505,21 +525,25 @@ define(
});
it("exposes a view-level selection proxy", function () {
expect(mockScope.selection.proxy).toHaveBeenCalledWith(
jasmine.any(Object)
);
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var selection = mockOpenMCT.selection.select.mostRecentCall.args[0];
expect(mockOpenMCT.selection.select).toHaveBeenCalled();
expect(selection.context.viewProxy).toBeDefined();
});
it("exposes drag handles", function () {
var handles;
// Select something so that drag handles are expected
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
controller.select(controller.getElements()[1]);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
// Should have a non-empty array of handles
handles = controller.handles();
expect(handles).toEqual(jasmine.any(Array));
expect(handles.length).not.toEqual(0);
@@ -532,15 +556,14 @@ define(
});
it("exposes a move handle", function () {
var handle;
// Select something so that drag handles are expected
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
controller.select(controller.getElements()[1]);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
// Should have a move handle
handle = controller.moveHandle();
var handle = controller.moveHandle();
// And it should have start/continue/end drag methods
expect(handle.startDrag).toEqual(jasmine.any(Function));
@@ -551,26 +574,40 @@ define(
it("updates selection style during drag", function () {
var oldStyle;
// Select something so that drag handles are expected
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
controller.select(controller.getElements()[1]);
mockScope.selection.get.andReturn(controller.getElements()[1]);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
// Get style
oldStyle = controller.selected().style;
oldStyle = controller.getSelectedElementStyle();
// Start a drag gesture
controller.moveHandle().startDrag();
// Haven't moved yet; style shouldn't have updated yet
expect(controller.selected().style).toEqual(oldStyle);
expect(controller.getSelectedElementStyle()).toEqual(oldStyle);
// Drag a little
controller.moveHandle().continueDrag([1000, 100]);
// Style should have been updated
expect(controller.selected().style).not.toEqual(oldStyle);
expect(controller.getSelectedElementStyle()).not.toEqual(oldStyle);
});
it("cleans up slection on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.mostRecentCall.args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
describe("on display bounds changes", function () {
@@ -660,7 +697,7 @@ define(
source: 'range'
}
]);
var key = controller.chooseTelemetryKeyToDisplay(mockMetadata);
var key = controller.chooseValueMetadataToDisplay(mockMetadata).source;
expect(key).toEqual('range');
});
@@ -682,7 +719,7 @@ define(
}
}
]);
var key = controller.chooseTelemetryKeyToDisplay(mockMetadata);
var key = controller.chooseValueMetadataToDisplay(mockMetadata).source;
expect(key).toEqual('image');
});
@@ -702,6 +739,14 @@ define(
expect(controller.getElements()[0].cssClass).toEqual("alarm-a");
});
});
it("listens for selection change events", function () {
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
});
});
}

View File

@@ -21,8 +21,14 @@
*****************************************************************************/
define(
["../src/LayoutController"],
function (LayoutController) {
[
"../src/LayoutController",
"zepto"
],
function (
LayoutController,
$
) {
describe("The Layout controller", function () {
var mockScope,
@@ -32,7 +38,12 @@ define(
controller,
mockCompositionCapability,
mockComposition,
mockCompositionObjects;
mockCompositionObjects,
mockOpenMCT,
mockSelection,
mockDomainObjectCapability,
$element = [],
selectable = [];
function mockPromise(value) {
return {
@@ -58,21 +69,18 @@ define(
} else {
return {};
}
},
getCapability: function () {
return mockDomainObjectCapability;
},
hasCapability: function (param) {
if (param === 'composition') {
return id !== 'b';
}
}
};
}
// Utility function to find a watch for a given expression
function findWatch(expr) {
var watch;
mockScope.$watch.calls.forEach(function (call) {
if (call.args[0] === expr) {
watch = call.args[1];
}
});
return watch;
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
@@ -88,7 +96,6 @@ define(
mockComposition = ["a", "b", "c"];
mockCompositionObjects = mockComposition.map(mockDomainObject);
testConfiguration = {
panels: {
a: {
@@ -97,27 +104,70 @@ define(
}
}
};
mockDomainObjectCapability = jasmine.createSpyObj('capability',
['inEditContext']
);
mockCompositionCapability = mockPromise(mockCompositionObjects);
mockScope.domainObject = mockDomainObject("mockDomainObject");
mockScope.model = testModel;
mockScope.configuration = testConfiguration;
mockScope.selection = jasmine.createSpyObj(
'selection',
['select', 'get', 'selected', 'deselect']
);
selectable[0] = {
context: {
oldItem: mockScope.domainObject
}
};
mockSelection = jasmine.createSpyObj("selection", [
'select',
'on',
'off',
'get'
]);
mockSelection.get.andReturn(selectable);
mockOpenMCT = {
selection: mockSelection
};
$element = $('<div></div>');
$(document).find('body').append($element);
spyOn($element[0], 'click');
spyOn(mockScope.domainObject, "useCapability").andCallThrough();
controller = new LayoutController(mockScope);
controller = new LayoutController(mockScope, $element, mockOpenMCT);
spyOn(controller, "layoutPanels").andCallThrough();
findWatch("selection")(mockScope.selection);
jasmine.Clock.useMock();
});
afterEach(function () {
$element.remove();
});
it("listens for selection change events", function () {
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
it("cleans up on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.calls[0].args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
// Model changes will indicate that panel positions
// may have changed, for instance.
it("watches for changes to composition", function () {
@@ -320,67 +370,35 @@ define(
.not.toEqual(oldStyle);
});
it("allows panels to be selected", function () {
it("allows objects to be selected", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.selected(childObj)).toBe(true);
});
it("prevents event bubbling while drag is in progress", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
controller.select(mockEvent, childObj.getId());
// Do a drag
controller.startDrag(childObj.getId(), [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
// Because mouse position could cause the parent object to be selected, this should be ignored.
controller.bypassSelection(mockEvent);
expect(mockEvent.stopPropagation).toHaveBeenCalled();
expect(controller.selected(childObj)).toBe(true);
});
it("allows selection to be cleared", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
controller.select(null, childObj.getId());
controller.clearSelection();
expect(controller.selected(childObj)).toBeFalsy();
});
it("prevents clearing selection while drag is in progress", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
var id = childObj.getId();
controller.select(mockEvent, id);
// Do a drag
controller.startDrag(id, [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
// Because mouse position could cause clearSelection to be called, this should be ignored.
controller.clearSelection();
expect(controller.selected(childObj)).toBe(true);
// Shoud be able to clear the selection after dragging is done.
// Shoud be able to select another object when dragging is done.
jasmine.Clock.tick(0);
controller.clearSelection();
mockEvent.stopPropagation.reset();
controller.bypassSelection(mockEvent);
expect(controller.selected(childObj)).toBe(false);
});
it("clears selection after moving/resizing", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
var id = childObj.getId();
controller.select(mockEvent, id);
// Do a drag
controller.startDrag(id, [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
jasmine.Clock.tick(0);
controller.clearSelection();
expect(controller.selected(childObj)).toBe(false);
expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
});
it("shows frames by default", function () {
@@ -398,43 +416,74 @@ define(
it("hides frame when selected object has frame ", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
controller.select(mockEvent, childObj.getId());
expect(mockScope.selection.select).toHaveBeenCalled();
var selectedObj = mockScope.selection.select.mostRecentCall.args[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
expect(controller.hasFrame(childObj)).toBe(true);
expect(selectedObj.hideFrame).toBeDefined();
expect(selectedObj.hideFrame).toEqual(jasmine.any(Function));
expect(toolbarObj.hideFrame).toBeDefined();
expect(toolbarObj.hideFrame).toEqual(jasmine.any(Function));
});
it("shows frame when selected object has no frame", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[1];
controller.select(mockEvent, childObj.getId());
expect(mockScope.selection.select).toHaveBeenCalled();
var selectedObj = mockScope.selection.select.mostRecentCall.args[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
expect(controller.hasFrame(childObj)).toBe(false);
expect(selectedObj.showFrame).toBeDefined();
expect(selectedObj.showFrame).toEqual(jasmine.any(Function));
expect(toolbarObj.showFrame).toBeDefined();
expect(toolbarObj.showFrame).toEqual(jasmine.any(Function));
});
it("deselects the object that is no longer in the composition", function () {
it("selects the parent object when selected object is removed", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
controller.select(mockEvent, childObj.getId());
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var composition = ["b", "c"];
mockScope.$watchCollection.mostRecentCall.args[1](composition);
expect(controller.selected(childObj)).toBe(false);
expect($element[0].click).toHaveBeenCalled();
});
it("allows objects to be drilled-in only when editing", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
childObj.getCapability().inEditContext.andReturn(false);
controller.drill(mockEvent, childObj);
expect(controller.isDrilledIn(childObj)).toBe(false);
});
it("allows objects to be drilled-in only if it has sub objects", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[1];
childObj.getCapability().inEditContext.andReturn(true);
controller.drill(mockEvent, childObj);
expect(controller.isDrilledIn(childObj)).toBe(false);
});
it("selects a newly-dropped object", function () {
mockScope.$on.mostRecentCall.args[1](
mockEvent,
'd',
{ x: 300, y: 100 }
);
var childObj = mockDomainObject("d");
var testElement = $("<div data-layout-id='some-id'></div>");
$element.append(testElement);
spyOn(testElement[0], 'click');
controller.selectIfNew('some-id', childObj);
jasmine.Clock.tick(0);
expect(testElement[0].click).toHaveBeenCalled();
});
});
}
);

View File

@@ -52,6 +52,12 @@ define([
beforeEach(function () {
$scope = jasmine.createSpyObj('$scope', ['$on']);
$scope.domainObject = { getCapability: function () {
return { getActions: function () {
return [];
}};
}};
$element = jasmine.createSpyObj('$element', [
'parent',
'remove',

View File

@@ -0,0 +1,314 @@
define([
"legacyRegistry",
"./src/controllers/NotebookController",
"./src/controllers/NewEntryController",
"./src/controllers/SelectSnapshotController",
"./src/controllers/LayoutNotebookController",
"./src/directives/MCTSnapshot",
"../layout/src/MCTTriggerModal",
"./src/directives/EntryDnd",
"./src/actions/ViewSnapshot",
"./src/actions/AnnotateSnapshot",
"./src/actions/RemoveEmbed",
"./src/actions/CreateSnapshot",
"./src/actions/RemoveSnapshot",
"./src/actions/NewEntryContextual",
"./src/capabilities/NotebookCapability",
"./src/policies/CompositionPolicy",
"./src/policies/ViewPolicy",
"text!./res/templates/layoutNotebook.html",
"text!./res/templates/notebook.html",
"text!./res/templates/entry.html",
"text!./res/templates/annotation.html",
"text!./res/templates/notifications.html",
"text!../layout/res/templates/frame.html",
"text!./res/templates/controls/embedControl.html",
"text!./res/templates/controls/snapSelect.html"
], function (
legacyRegistry,
NotebookController,
NewEntryController,
SelectSnapshotController,
LayoutNotebookController,
MCTSnapshot,
MCTModalNotebook,
MCTEntryDnd,
ViewSnapshotAction,
AnnotateSnapshotAction,
RemoveEmbedAction,
CreateSnapshotAction,
RemoveSnapshotAction,
newEntryAction,
NotebookCapability,
CompositionPolicy,
ViewPolicy,
layoutNotebookTemplate,
notebookTemplate,
entryTemplate,
annotationTemplate,
notificationsTemplate,
frameTemplate,
embedControlTemplate,
snapSelectTemplate
) {
legacyRegistry.register("platform/features/notebook", {
"name": "Notebook Plugin",
"description": "Create and save timestamped notes with embedded object snapshots.",
"extensions":
{
"types": [
{
"key": "notebook",
"name": "Notebook",
"cssClass": "icon-notebook",
"description": "Create and save timestamped notes with embedded object snapshots.",
"features": ["creation"],
"model": {
"entries": [],
"composition": [],
"entryTypes": []
}
}
],
"views": [
{
"key": "notebook.view",
"type": "notebook",
"cssClass": "icon-notebook",
"name": "notebook",
"template": notebookTemplate,
"editable": false,
"uses": [
"composition",
"action"
],
"gestures": [
"drop"
]
}
],
"controllers": [
{
"key": "NotebookController",
"implementation": NotebookController,
"depends": ["$scope",
"dialogService",
"popupService",
"agentService",
"objectService",
"navigationService",
"now",
"actionService",
"$timeout",
"$element",
"$sce"
]
},
{
"key": "NewEntryController",
"implementation": NewEntryController,
"depends": ["$scope",
"$rootScope"
]
},
{
"key": "selectSnapshotController",
"implementation": SelectSnapshotController,
"depends": ["$scope",
"$rootScope"
]
},
{
"key": "LayoutNotebookController",
"implementation": LayoutNotebookController,
"depends": ["$scope"]
}
],
"representations": [
{
"key": "draggedEntry",
"template": entryTemplate
},
{
"key": "frameLayoutNotebook",
"template": frameTemplate
}
],
"templates": [
{
"key": "annotate-snapshot",
"template": annotationTemplate
},
{
"key": "notificationTemplate",
"template": notificationsTemplate
}
],
"directives": [
{
"key": "mctSnapshot",
"implementation": MCTSnapshot,
"depends": [
"$rootScope",
"$document",
"exportImageService",
"dialogService",
"notificationService"
]
},
{
"key": "mctEntryDnd",
"implementation": MCTEntryDnd,
"depends": [
"$rootScope",
"$compile",
"dndService",
"typeService",
"notificationService"
]
},
{
"key": "mctModalNotebook",
"implementation": MCTModalNotebook,
"depends": [
"$document"
]
}
],
"actions": [
{
"key": "view-snapshot",
"implementation": ViewSnapshotAction,
"name": "View Snapshot",
"description": "View the large image in a modal",
"category": "embed",
"depends": [
"$compile"
]
},
{
"key": "annotate-snapshot",
"implementation": AnnotateSnapshotAction,
"name": "Annotate Snapshot",
"cssClass": "icon-pencil labeled",
"description": "Annotate embed's snapshot",
"category": "embed",
"depends": [
"dialogService",
"dndService",
"$rootScope"
]
},
{
"key": "remove-embed",
"implementation": RemoveEmbedAction,
"name": "Remove...",
"cssClass": "icon-trash labeled",
"description": "Remove this embed",
"category": [
"embed",
"embed-no-snap"
],
"depends": [
"dialogService"
]
},
{
"key": "remove-snapshot",
"implementation": RemoveSnapshotAction,
"name": "Remove Snapshot",
"cssClass": "icon-trash labeled",
"description": "Remove Snapshot of the embed",
"category": "embed",
"depends": [
"dialogService"
]
},
{
"key": "create-snapshot",
"implementation": CreateSnapshotAction,
"name": "Create Snapshot",
"description": "Create a snapshot for the embed",
"category": "embed-no-snap",
"priority": "preferred",
"depends": [
"$compile"
]
},
{
"key": "notebook-new-entry",
"implementation": newEntryAction,
"name": "New Notebook Entry",
"cssClass": "icon-notebook labeled",
"description": "Add a new Notebook entry",
"category": [
"contextual",
"view-control"
],
"depends": [
"$compile",
"$rootScope",
"dialogService",
"notificationService",
"linkService"
],
"priority": "preferred"
}
],
"licenses": [
{
"name": "painterro",
"version": "4.1.0",
"author": "Mike Bostock",
"description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.",
"website": "https://d3js.org/",
"copyright": "Copyright 2010-2016 Mike Bostock",
"license": "BSD-3-Clause",
"link": "https://github.com/d3/d3/blob/master/LICENSE"
}
],
"capabilities": [
{
"key": "notebook",
"name": "Notebook Capability",
"description": "Provides a capability for looking for a notebook domain object",
"implementation": NotebookCapability,
"depends": [
"typeService"
]
}
],
"policies": [
{
"category": "composition",
"implementation": CompositionPolicy,
"message": "Objects of this type cannot contain objects of that type."
}
],
"controls": [
{
"key": "embed-control",
"template": embedControlTemplate
},
{
"key": "snapshot-select",
"template": snapSelectTemplate
}
],
"stylesheets": [
{
"stylesheetUrl": "css/notebook.css"
},
{
"stylesheetUrl": "css/notebook-espresso.css",
"theme": "espresso"
},
{
"stylesheetUrl": "css/notebook-snow.css",
"theme": "snow"
}
]
}
});
});

View File

@@ -0,0 +1,283 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
.w-notebook {
font-size: 0.8rem;
overflow: hidden;
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
width: auto;
height: auto;
}
.l-notebook-drag-area {
padding: 10px;
font-style: italic;
cursor: pointer;
&:before { margin-right: 7px !important; }
.label {
@include ellipsize();
}
}
.frame {
.icon-notebook {
margin-right: 5px;
}
}
.overlay.l-dialog .title{
white-space: normal;
}
.w-notebook-entries {
//@include test($a: 0.1);
padding-right: $interiorMarginSm;
position: relative;
overflow-x: hidden;
overflow-y: scroll;
.t-entries-list {
}
}
.l-notebook-entry {
$p: $interiorMarginSm;
box-sizing: border-box;
margin-bottom: $p;
padding: $p $interiorMargin;
.s-notebook-entry-time,
.s-notebook-entry-text,
.notebook-entry-delete {
padding-top: $p;
padding-bottom: $p;
}
.s-notebook-entry-time {
border: 1px solid transparent; // Needed to maintain vertical alignment with s-notebook-entry-text
}
.l-notebook-entry-content{
.s-notebook-entry-text {
// Contenteditable div that holds text
min-height: 24px; // Needed in Firefox when field is blank
}
.entry-embeds{
flex-wrap: wrap;
}
.snap-thumb {
cursor: pointer;
}
}
}
.l-entry-embed {
$m: $interiorMarginSm;
position: relative;
margin: $m $m 0 0;
padding: $interiorMarginSm;
&.has-snapshot {
&:before {
position: absolute;
text-shadow: rgba(black, 0.7) 0 1px 5px;
z-index: 2;
}
}
.snap-thumb {
$d: 50px;
width: $d;
height: $d;
border-radius: 5px;
overflow: hidden;
img {
height: 100%;
width: 100%;
}
}
.embed-info {
margin-left: $interiorMargin;
.embed-title {
font-weight: bold;
}
}
}
.t-contents,
.snap-annotation {
// Todo: don't write this to t-contents, add a l- class
overflow: hidden;
}
.notebook-filters {
.select {
margin-left: $interiorMargin;
}
}
/********************************************* MOBILE */
@include phonePortrait() {
body.phone {
.w-notebook-entry-time-and-content {
flex-direction: column !important;
}
.s-notebook-entry-time,
.notebook-entry-delete {
padding-top: 0;
padding-bottom: 0;
}
}
}
/********************************************* PAINTERRO OVERRIDES */
.annotation-dialog .abs.editor {
border-radius: 0;
}
#snap-annotation {
display: flex;
flex-direction: column;
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
}
#snap-annotation-wrapper,
#snap-annotation-bar {
position: relative;
top: auto; right: auto; bottom: auto; left: auto;
}
#snap-annotation-wrapper {
order: 2;
flex: 10 0 auto;
}
#snap-annotation-bar {
order: 1;
flex: 0 0 auto;
height: auto;
background-color: transparent !important;
margin-bottom: $interiorMargin;
> div,
> div > span,
.ptro-icon-btn,
.ptro-named-btn,
.ptro-color-btn,
.ptro-bordered-btn,
.ptro-tool-ctl-name,
.ptro-color-btn,
.tool-controls,
.ptro-input {
// Lot of resets for crappy CSS in Painterro
&:first-child {
margin-left: 0 !important;
}
$h: $btnToolbarH;
display: inline-block;
font-family: inherit;
font-size: auto;
height: $h !important;
margin: 0 0 0 5px;
position: relative;
width: auto !important;
line-height: $h !important;
top: auto;
right: auto;
bottom: auto;
left: auto;
vertical-align: top;
}
.ptro-tool-ctl-name {
border-radius: 0;
background: none;
top: auto;
font-family: inherit;
padding: 0;
}
.ptro-color-btn {
width: $btnToolbarH !important;
}
.ptro-icon-btn,
.ptro-named-btn {
// .s-button class is added via JS in AnnotateSnapshot.js
// TODO: redo this so that we don't need to use Zepto and JS
i {
font-size: 1.25em !important;
}
}
.tool-controls {
font-size: 0.8rem !important;
}
.ptro-info,
.ptro-btn-color-checkers-bar,
*[title="Font name"],
*[title="Stroke color"],
*[title="Stroke width"],
*[data-id="fontName"],
*[data-id="fontStrokeSize"],
*[data-id="stroke"] {
display: none;
}
}
/********************************************* NO IDEA WHAT THERE ARE APPLYING TO */
.context-available {
outline: none;
}
.menu-element.menu-view{
z-index: 999;
}
.overlay.l-dialog .abs.editor {
padding-right: 0;
}
/*
.overlay.l-dialog .outer-holder.annotation-dialog{
width: 90%;
height: 90%;
}
*/
/*
.snap-annotation-wrapper{
padding-top: 40px;
}
.t-console {
// Temp console-like reporting element
max-height: 200px;
box-sizing: border-box;
padding: 5px;
}
*/

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