Compare commits

..

162 Commits

Author SHA1 Message Date
Charles Hacskaylo
4087b9cdde [Frontend] Styling for Time Conductor v2
Fixes #933
WIP: Fixed look for Firefox
2016-07-15 14:39:29 -07:00
Charles Hacskaylo
43a804eef4 [Frontend] Styling for Time Conductor v2
Fixes #933
WIP: Added zoom current range indicator;
tweaks to style
2016-07-15 07:54:32 -07:00
Charles Hacskaylo
b3a4f52fe2 [Frontend] Styling for Time Conductor v2
Fixes #933
WIP: Adding zoom control with HTML5
input range type; Refactored sass slightly
to move display: inline-block out of mixin
containerBase and into .s-btn.
2016-07-14 18:30:49 -07:00
Charles Hacskaylo
671e3016d4 [Frontend] Styling for Time Conductor v2
Fixes #933
New _animations scss include, moved
scss around.
2016-07-14 16:40:05 -07:00
Charles Hacskaylo
379828315f [Frontend] Styling for Time Conductor v2
Fixes #933
New _animations scss include, moved
scss around.
2016-07-14 16:39:27 -07:00
Charles Hacskaylo
8c5538ec4d [Frontend] Styling for Time Conductor v2
Fixes #933
"Sticky" clock anim for LAD mode
2016-07-14 14:58:18 -07:00
Charles Hacskaylo
8b694ef337 [Frontend] Styling for Time Conductor v2
Fixes #933
In-progress: color/size tweaks, fixes for espresso
theme
2016-07-13 19:42:51 -07:00
Charles Hacskaylo
e193e3dfba Merge open933 latest, resolve conflicts
Fixes #933
Fair amount of manual fixing in time-conductor.html
2016-07-13 19:16:27 -07:00
Charles Hacskaylo
8214c8e895 Merge open933 latest, resolve conflicts
Fixes #933
Fair amount of manual fixing in time-conductor.html
2016-07-13 19:16:00 -07:00
Charles Hacskaylo
33b2225d10 [Frontend] Styling for Time Conductor v2
Fixes #933
In-progress: restructured markup to move
modeModel farther out; animated icon
2016-07-13 18:48:17 -07:00
Henry
14463d39a8 Added end delta 2016-07-13 18:17:27 -07:00
Charles Hacskaylo
fcfda50e73 [Frontend] Styling for Time Conductor v2
Fixes #933
In-progress: color tweaks, bar sizing,
field widths
2016-07-13 15:00:16 -07:00
Charles Hacskaylo
06af84c161 [Frontend] Styling for Time Conductor v2
Fixes #933
In-progress: fixed SVG text color, field
styling for fixed vs. real-time, markup cleanup
2016-07-13 13:14:32 -07:00
Charles Hacskaylo
5238aa2731 [Frontend] Styling for Time Conductor v2
Fixes #933
In-progress; fixed SVG text color
2016-07-13 08:07:59 -07:00
Henry
fd29473664 Support resize 2016-07-12 15:02:39 -07:00
Henry
97f3fd516b Changed default duration to fifteen minutes 2016-07-12 10:14:26 -07:00
Henry
088416905d Added duration 2016-07-12 10:08:08 -07:00
Henry
2056d87453 Merge branch 'open933-frontend' into open933 2016-07-11 14:27:30 -07:00
Charles Hacskaylo
64ce8a2b2a [Frontend] Styling for Time Conductor v2
Fixes #933
Color adjusts, mini super-menu size
and font tweaks, glyphs added to selector,
SVG style fixes in progress
2016-07-11 14:03:41 -07:00
Henry
585da38a16 Fixed some merge issues 2016-07-11 13:46:46 -07:00
Charles Hacskaylo
bf0e85a94c [Frontend] Styling for Time Conductor v2
Fixes #933
Renamed main class; removed unused <style>
defs
2016-07-11 11:35:26 -07:00
Charles Hacskaylo
84b7a9dc2f Merge remote-tracking branch 'origin/open933' into open933-frontend
Fixes #933
Conflicts:
	platform/features/conductor-v2/src/TimeConductorController.js
2016-07-11 11:29:35 -07:00
Henry
11caa8396a Updated modes 2016-07-11 11:18:23 -07:00
Henry
0017b77439 Merged markup changes 2016-07-11 11:06:22 -07:00
Charles Hacskaylo
7b7b21d748 [Frontend] Styling of Time Conductor v2
Fixes #933
Tweaks to language; tweak to class name in markup
2016-07-11 11:05:47 -07:00
Charles Hacskaylo
788483ec13 [Frontend] Styling of Time Conductor v2
Fixes #933
Tweaks to language
2016-07-11 10:37:08 -07:00
Charles Hacskaylo
7b19f91ce6 [Frontend] Merge latest from open933
Fixes #933
Resolve conflicts in
mode-menu.html, mode-selector.html,
time-conductor.html; apply tweaks, language, etc.
2016-07-11 10:31:14 -07:00
Henry
5cc81ba12a [Time Conductor] Added mode class to time conductor 2016-07-11 10:26:34 -07:00
Charles Hacskaylo
0a0bc55f5f [Frontend] Styling for Time Conductor v2
Fixes #993
In-progress; mode menu names and
descriptors modified, markup cleaned up
2016-07-08 17:11:43 -07:00
Henry
4e7b69c4df Enabled fixed and real-time modes 2016-07-08 16:57:50 -07:00
Charles Hacskaylo
cf83040c4b [Frontend] Styling for Time Conductor v2
Fixes #993
In-progress; Create menu refactoring and new
mini Create menu
2016-07-08 16:54:49 -07:00
Charles Hacskaylo
32f7bc86af [Frontend] Styling for Time Conductor v2
Fixes #993
In-progress; class renaming continued,
cleanups in markup file, in-page styles
ported to scss
2016-07-08 16:54:13 -07:00
Henry
e230b92946 Fixed bug with date selector having to be clicked twice 2016-07-08 15:15:12 -07:00
Henry
58ed500ecf Time sync via conductor 2016-07-07 16:57:03 -07:00
Henry
bca5eb0fdb [Time Conductor] #933 Initial markup 2016-07-07 09:47:46 -07:00
Pete Richards
652a50c700 Merge remote-tracking branch 'origin/open1018-new' 2016-06-27 09:13:03 -07:00
Victor Woeltjen
736cba76bb Merge pull request #1056 from nasa/blocking-save-dialog
[Save] Show blocking dialog
2016-06-26 11:07:03 -07:00
Andrew Henry
2339560363 [Time Conductor] Bounds updated when date selected from date selector. Fixes #1018 2016-06-24 21:50:59 -07:00
Pete Richards
24e870a126 [Save] Show blocking dialog
Show a blocking dialog while the save action is being performed.

Prevents users from pressing save a second time or performing
further actions while a save is in progress.

Fixes https://github.jpl.nasa.gov/MissionControl/vista/issues/362
2016-06-24 20:01:52 -07:00
Victor Woeltjen
8bdf1e3072 [Build] Bump version number
...and restore SNAPSHOT status, to begin sprint Le Guin,
https://github.com/nasa/openmct/milestones/Le%20Guin
2016-06-24 15:42:10 -07:00
Victor Woeltjen
e8e9598721 [Build] Remove SNAPSHOT status
...to close sprint Kress,
https://github.com/nasa/openmct/milestones/Kress
2016-06-24 15:37:23 -07:00
Andrew Henry
ce87ad2564 Merge pull request #916 from ev1stensberg/patch-2
[Branding] Added Apache license to README.md
2016-06-23 11:43:16 -07:00
Andrew Henry
2a2f6e8142 Merge pull request #915 from ev1stensberg/patch-1
[Functionality] Log app instance to the console
2016-06-23 11:24:53 -07:00
Andrew Henry
3e5057c285 Merge pull request #1015 from mockingjamie/master
Proofread readme.md
2016-06-23 11:24:04 -07:00
Charles Hacskaylo
5485950130 Merge pull request #1049 from nasa/update-plot-squish-margin
[Plot] Set min height
2016-06-22 11:36:19 -07:00
Pete Richards
c8f4568bd0 [Plot] Set min height
Set the min height for the plot element based on user feedback for
minimum plot size that they find useful.  Plot ticks may overlap
but that is expected to be fixed in a future release.

Fixes https://github.com/nasa/openmct/issues/1048
2016-06-22 10:51:16 -07:00
Victor Woeltjen
cefb40856b Merge pull request #1042 from nasa/open1040
[Tables] Enabled auto-scroll by default fixes #1040
2016-06-20 16:24:58 -07:00
Henry
ee7c450e11 [Tables] Enabled auto-scroll by default fixes #1040 2016-06-20 16:18:26 -07:00
Victor Woeltjen
d741e0f23c Merge pull request #1041 from nasa/frontend-1014a
Emphasis styling applied to Edit button properly
2016-06-20 16:13:29 -07:00
Charles Hacskaylo
8080490e5c [Frontend] Applied emphasis colors to .key-properties
fixes #1014
Added .key-properties to .major to color the Edit
button as intended;
2016-06-20 16:06:23 -07:00
Victor Woeltjen
a3443d8077 Merge pull request #1025 from nasa/frontend-1019
R&I fixes for Snow button colors and Timeline edit Add menu
2016-06-20 12:36:29 -07:00
Victor Woeltjen
53e8e7c688 Merge pull request #1024 from nasa/plots-1022
R&I un-squished plots fix
2016-06-17 10:36:40 -07:00
Charles Hacskaylo
dea94e4e68 [Frontend] Fix button colors in Snow theme
Fixes #1014
- Problem was actually application of a color
within the .icon class - fix removes that
definition;
2016-06-16 17:55:40 -07:00
Charles Hacskaylo
d40c7f1821 [Frontend] Restore the Add menu to edit Timelines
Fixes #1019
- Removed overflow: hidden from .l-edit-controls;
2016-06-16 17:46:15 -07:00
Charles Hacskaylo
c7e7e0c302 [Frontend] Fixes to un-squish plots
Fixes #1022
- min-height added to .gl-plot element;
- Moved plot value definitions out of _plots-main
and into _constants and normalized naming;
2016-06-16 17:24:44 -07:00
Pete Richards
8ac6ac97f0 Merge remote-tracking branch 'origin/open972' 2016-06-16 11:08:43 -07:00
Victor Woeltjen
1afa4ab329 Merge pull request #1017 from nasa/open664
[Edit Mode] Canceling edit mode with unsaved changes now shows confirmation dialog to user
2016-06-16 09:31:37 -07:00
Henry
c2517c1670 [Edit Mode] Canceling edit mode with unsaved changes now shows confirmation dialog to user. Fixes #664 2016-06-15 17:10:29 -07:00
Andrew Henry
717ceff02c Merge pull request #995 from nasa/timeline-913a
R&I tweaks to Timeline scrollbar and splitters
2016-06-15 11:45:17 -07:00
Victor Woeltjen
6878c59f45 Merge pull request #1016 from nasa/fix-1013
[Persistence] Use identifier service to get key
2016-06-15 11:18:13 -07:00
Pete Richards
c00b053aa7 [Tests] verify that identifier service provides key
Verify that the results of the identifier service are used for
persistence calls instead of the domain object id.

https://github.com/nasa/openmct/issues/1013
2016-06-15 10:31:25 -07:00
Victor Woeltjen
480e12c3ab Merge pull request #986 from nasa/table-export-934
[Table] Add Export for table contents
2016-06-15 10:17:21 -07:00
Victor Woeltjen
50bd233b0a Merge remote-tracking branch 'origin/master' into table-export-934
Conflicts:
	platform/commonUI/general/res/sass/controls/_buttons.scss
	platform/features/table/res/templates/mct-table.html
2016-06-15 10:05:20 -07:00
Henry
79406cf1ed [Tables] #972 Refactored to simplify code. Fixes #972 2016-06-14 16:34:07 -07:00
Pete Richards
2d5824c4ab [Persistence] Use identifier service to get key
Retrieve key from identifier service for all persist operations.

Fixes https://github.com/nasa/openmct/issues/1013
2016-06-14 10:56:54 -07:00
Pete Richards
3480809129 Merge remote-tracking branch 'origin/open629' 2016-06-14 10:44:58 -07:00
Pete Richards
11bc48c7e0 Merge remote-tracking branch 'origin/timeline-console-error-978' 2016-06-14 10:41:26 -07:00
Pete Richards
d759401b69 Merge remote-tracking branch 'origin/open1000' 2016-06-14 10:38:51 -07:00
mockingjamie
5a2e5ac48f Merge pull request #1 from mockingjamie/changes-to-readme.md
Proofread readme.md
2016-06-13 21:27:43 -04:00
mockingjamie
1144f818cf Proofread readme.md
Made minor grammatical corrections.
2016-06-13 21:27:28 -04:00
Victor Woeltjen
0aebecfbb0 Merge pull request #987 from nasa/anim-to-edit-709
[Frontend] Animation added to transition from browse to edit mode
2016-06-13 13:31:49 -07:00
Pete Richards
531d40993a Merge remote-tracking branch 'origin/better-dialog-message-339' 2016-06-10 14:55:07 -07:00
Pete Richards
4c097faf88 Merge remote-tracking branch 'origin/timeline-flash-997' 2016-06-10 14:50:36 -07:00
Victor Woeltjen
5152e64895 [Duplicate] Allow copy across spaces
Fixes #1007
2016-06-10 11:38:55 -07:00
Victor Woeltjen
0263237b2c Merge pull request #1006 from nasa/open861
[Tables] Recalculate column dimensions on resize
2016-06-09 10:21:05 -07:00
Andrew Henry
acd0fae040 [Tables] Recalculate column dimensions on resize. Fixes #861 2016-06-09 12:59:46 +01:00
Andrew Henry
29dd51439d [Date Input] Addressed issues with date selector. Fixes #1000 2016-06-08 17:37:26 +01:00
Victor Woeltjen
a1b2175801 [Timeline] Reduce flicker
Reposition scroll bar in Timeline with RAF instead of timeout;
this ensures that scroll bar is positioned after the current
digest (updating the width) but before the results are rendered
(avoiding flicker.) Fixes #997
2016-06-07 10:05:51 -07:00
Victor Woeltjen
b0f06a2195 [Build] Restore SNAPSHOT status
...to open sprint Kress,
https://github.com/nasa/openmct/milestones/Kress
2016-06-06 09:57:09 -07:00
Victor Woeltjen
f9c93ca022 [Build] Remove SNAPSHOT status
To close sprint Huxley,
https://github.com/nasa/openmct/milestones/Huxley
2016-06-06 09:49:52 -07:00
Charles Hacskaylo
8e0858bb24 [Frontend] Tweaks to splitter dimensions
Fixes #913
- Tightened up splitter height and width;
- Added hover color for snow theme;
2016-06-03 10:00:16 -07:00
Victor Woeltjen
ee0fa0451a Merge branch 'master' into timeline-zoom-center-936
Conflicts:
	platform/features/timeline/src/controllers/TimelineZoomController.js
	platform/features/timeline/test/controllers/TimelineZoomControllerSpec.js
2016-06-03 09:18:01 -07:00
Victor Woeltjen
e86e955682 Merge pull request #993 from nasa/revert-990-timeline-regression-817
Revert "[Timeline] Provide greater initial width"
2016-06-02 17:05:11 -07:00
Victor Woeltjen
9913fb48f5 Revert "[Timeline] Provide greater initial width" 2016-06-02 16:54:59 -07:00
Victor Woeltjen
71c362f016 Merge pull request #992 from nasa/revert-991-timeline-regression-817
Revert "[Timeline] Update scroll position on timeout"
2016-06-02 16:54:48 -07:00
Victor Woeltjen
fa6e8fd5f9 Revert "[Timeline] Update scroll position on timeout" 2016-06-02 16:49:49 -07:00
Victor Woeltjen
23b64951f3 [Timeline] Update zoom controller spec
...to reflect changes/simplifications for #936.
2016-06-02 16:42:09 -07:00
Victor Woeltjen
99590d18f7 [Timeline] Simplify bounds-tracking 2016-06-02 16:40:07 -07:00
Victor Woeltjen
86b31bc040 [Timeline] Simplify scroll-setting 2016-06-02 16:38:11 -07:00
Victor Woeltjen
d52bfed1df [Timeline] Always set scroll on timeout
...to allow time for width to increase.
2016-06-02 16:36:09 -07:00
Charles Hacskaylo
808ccd0376 [Frontend] Tweaks to splitter
Fixes #913
- IN PROGRESS: working on making
smaller splitter in Timelines
2016-06-02 16:23:42 -07:00
Victor Woeltjen
44d6456de1 [Timeline] Set scroll on timeout
Whenever timeline zoom controller sets scroll, check to see if
there may be a width violation that causes scroll to be reset,
and retry on a timeout if so. Fixes #936.
2016-06-02 16:05:28 -07:00
Charles Hacskaylo
a394b95259 [Frontend] Tweaks to scrollbar visibility
Fixes #913
- Scrollbar now always visible in the right side
of the tabular area, in order to avoid column
bottom edges not aligning;
2016-06-02 16:04:26 -07:00
Victor Woeltjen
beeefe517a Merge pull request #991 from nasa/timeline-regression-817
[Timeline] Update scroll position on timeout
2016-06-02 15:37:42 -07:00
Victor Woeltjen
d02f4041b2 [Timeline] Update scroll position on timeout
Fixes #817
2016-06-02 15:34:32 -07:00
Victor Woeltjen
9fd75ff91e Merge pull request #990 from nasa/timeline-regression-817
[Timeline] Provide greater initial width
2016-06-02 14:59:14 -07:00
Victor Woeltjen
026ece3956 [Timeline] Provide greater initial width
This avoids starting with a scrollable width too small for the
initial scroll position that the zoom controller selects.
Fixes #817
2016-06-02 14:48:58 -07:00
Charles Hacskaylo
214a843dba [Frontend] New message for copy in-progress dialog
fixes #339
2016-06-02 11:09:22 -07:00
Andrew Henry
1d6880c283 Merge pull request #982 from nasa/timeline-scroll-981
[Timeline] Use reasonable width for scroll area
2016-06-02 16:59:08 +01:00
Andrew Henry
8ddad9bf4c Merge pull request #984 from nasa/open979
[Edit] Fixed issue with cancel action throwing an error. Fixes #979
2016-06-02 10:31:20 +01:00
Andrew Henry
f167022eea Changed logic of persisted check slightly 2016-06-02 10:26:38 +01:00
Charles Hacskaylo
76edba1014 [Frontend] Mods to Edit button
fixes #709
- Changed title and style of main Edit button;
- Updated EditItem.js protractor ID accordingly;
2016-06-01 19:19:58 -07:00
Charles Hacskaylo
7904989a23 [Frontend] Added transitional animation to Edit mode
fixes #709
- When going from browse to edit mode, the wrapper
around the object being edited will now transition in
from its edges, and the edit controls toolbar will
animate its height;
- There is no transition applied for going from edit
to browse; to do this we'd need to mod the JS on
exiting to look for the end of an animation event;
- Tested in Chrome, Safari and Firefox;
- May not be smooth with very complex objects
like Layouts with a large number of components;
- Added transitional animations to .l-object-wrapper
and .l-edit-controls;
- New 'animTo' mixin added to _effects.scss;
2016-06-01 19:18:55 -07:00
Victor Woeltjen
ea676b4368 Merge remote-tracking branch 'origin/master' into table-export-934 2016-06-01 10:33:53 -07:00
Victor Woeltjen
cc2b102256 Merge pull request #985 from nasa/open973
[Style] Export button in tabular views
2016-06-01 10:27:51 -07:00
Andrew Henry
b1266abf01 [Edit] Fixed issue with cancel action throwing an error. Fixes #979 2016-06-01 10:41:01 +01:00
Victor Woeltjen
cc7d0477e8 [Timeline] Check for existence of timespan
...before attempting to calculate a width based on it.
Fixes #978.
2016-05-31 16:41:30 -07:00
Victor Woeltjen
5a2d1a746d [Timeline] Add missing semicolon 2016-05-31 16:27:59 -07:00
Victor Woeltjen
4f0e3fdf85 [Timeline] Test zoom controller's width 2016-05-31 16:21:40 -07:00
Victor Woeltjen
be9f56107c [Timeline] Remove obsolete test cases 2016-05-31 16:15:57 -07:00
Victor Woeltjen
787f3815df [Timeline] Expose width from ZoomController
...and ensure that the width exposed is not excessively
large; fixes #981
2016-05-31 16:12:49 -07:00
Victor Woeltjen
35d7d9b380 [Timeline] Remove width method
...from TimelineController. Will replace with a more straightforward
call to the zoom controller that uses the exposed end time instead,
to address #981.
2016-05-31 16:05:06 -07:00
Charles Hacskaylo
8b9c51f303 [Frontend] Styling Export button
fixes #973
- Done;
- Styling for Export button and tabular view
area in layout frame context;
- Export button in frame context now
hidden until user hovers over tabular
view area in frame, includes animated
transition;
- Normalized line-height on button and
menu elements in frame context;
- Layout/markup/SASS for historical and
RT tabular view modified;
- Converted imagery.html layout
to use flexbox;
2016-05-31 11:40:31 -07:00
Charles Hacskaylo
661b3d5889 [Frontend] Styling Export button
fixes #973
- In progress:
- Added new download symbol to symbols font;
- Added export symbol to export button;
- Layout/markup/SASS for historical tabular
view modified;
2016-05-31 09:50:12 -07:00
Andrew Henry
01c85cb58d [Table] #972 Further refactoring, tests, style fixes 2016-05-31 12:05:40 +01:00
Henry
0218f42e2b removed redundant code 2016-05-27 15:25:06 -07:00
Henry
d8d0f22889 Second rewrite 2016-05-27 15:14:46 -07:00
Henry
d8a097a95a [Tables] Added timeouts to yield control to ui thread. Fixes #972 2016-05-27 15:14:45 -07:00
Andrew Henry
dc577d4c24 Merge pull request #974 from nasa/open970
R&I open970: hide nav-to-parent arrow when in Edit mode
2016-05-27 14:56:48 -07:00
Andrew Henry
8f9308de01 Merge pull request #962 from nasa/csv-export-update-751
[Timeline] Updates to CSV Export
2016-05-27 14:55:59 -07:00
Andrew Henry
8f7a5e113b Merge pull request #951 from nasa/orphan-navigation-765
[Navigation] Prevent navigation to orphan objects
2016-05-27 14:53:33 -07:00
Charles Hacskaylo
9820f9d9c5 [Frontend] Mod CSS to properly hide nav-to-parent when editing
fixes #970
Not sure what problem was, but betting this was due to removal
of an ng-class previously in markup;
2016-05-26 12:45:25 -07:00
Victor Woeltjen
56ff98cce7 Merge branch 'master' into orphan-navigation-765 2016-05-26 12:35:48 -07:00
Victor Woeltjen
dade6b2254 [Timeline] Use positive logic for clarity
https://github.com/nasa/openmct/pull/962#discussion_r64678013
2016-05-26 12:12:52 -07:00
Victor Woeltjen
e9cac6eff3 [Timeline] Add JSDoc for new parameter
https://github.com/nasa/openmct/pull/962#discussion_r64677520
2016-05-26 12:12:52 -07:00
Henry
7fc2fcfa07 [Edit Mode] #629 Rewrote obsolete DomainColumn tests 2016-05-26 11:55:13 -07:00
Victor Woeltjen
5689279954 [Timeline] Add JSDoc for idMap
https://github.com/nasa/openmct/pull/962#discussion_r64676750
https://github.com/nasa/openmct/pull/962#discussion_r64677198
2016-05-26 11:53:16 -07:00
Henry
165e158f37 [Edit Mode] #629 Removed redundant test from DomainObjectProviderSpec 2016-05-26 11:44:43 -07:00
Henry
f301741852 [Edit Mode] #629 restored disabled tests for DropGesture. Removed extraneous argument to broadcast function 2016-05-26 11:42:15 -07:00
Henry
eda1f23df9 [Edit Mode] #629 rewriting disabled tests 2016-05-26 11:38:12 -07:00
Victor Woeltjen
d15d27af73 Merge branch 'master' into table-export-934 2016-05-26 10:42:35 -07:00
Victor Woeltjen
438511c5f7 [Table] Test CSV Export behavior 2016-05-25 15:07:17 -07:00
Victor Woeltjen
3eb960cf5a [Table] Move export button out of scroll 2016-05-25 14:50:06 -07:00
Victor Woeltjen
699f6ba458 [Table] Export table contents as CSV 2016-05-25 13:54:10 -07:00
Victor Woeltjen
f21f22d95c [Table] Tweak appearance of export button 2016-05-25 13:43:44 -07:00
Victor Woeltjen
b520d08818 [Table] Begin work on CSV export
#934
2016-05-25 13:23:19 -07:00
Victor Woeltjen
f9fd97230f [Timeline] Satisfy JSHint 2016-05-25 12:37:03 -07:00
Victor Woeltjen
536e2290b8 Merge branch 'master' into csv-export-update-751 2016-05-25 12:33:43 -07:00
Victor Woeltjen
73b922facf [Timeline] Update specs for indexes instead of ids 2016-05-25 12:32:44 -07:00
Victor Woeltjen
ba0d9a186b [Timeline] Account for new argument in spec 2016-05-25 12:26:47 -07:00
Victor Woeltjen
80f5cb756d [Timeline] Account for new argument in spec 2016-05-25 12:25:58 -07:00
Victor Woeltjen
d7f566088f [Timeline] Update spec to include logging 2016-05-25 12:25:02 -07:00
Victor Woeltjen
a3bcaea7f9 [Timeline] Show units in utilization headers 2016-05-25 12:21:58 -07:00
Victor Woeltjen
23c71b7218 [Timeline] Include units for utilizations 2016-05-25 12:12:11 -07:00
Victor Woeltjen
463f7ccf65 [Timeline] Use indexes instead of UUIDs 2016-05-25 12:07:35 -07:00
Victor Woeltjen
87fe407739 Merge branch 'master' into csv-export-update-751 2016-05-25 12:00:43 -07:00
Victor Woeltjen
bb4f1ce7cd [Timeline] Include utilization columns 2016-05-25 10:52:25 -07:00
Victor Woeltjen
0cc2ba7595 [Timeline] Import UtilizationColumn 2016-05-25 10:38:00 -07:00
Victor Woeltjen
8162429106 [Timeline] Pass in resources extensions 2016-05-25 10:37:01 -07:00
Victor Woeltjen
ed519d89d7 [Timeline] Log errors during CSV export
#751
2016-05-25 10:35:29 -07:00
Victor Woeltjen
0e4f6185b8 Merge branch 'master' into csv-export-update-751
Conflicts:
	platform/features/timeline/bundle.js
2016-05-25 10:29:56 -07:00
Victor Woeltjen
1ced47fc2c [Navigation] Prevent navigation to orphan objects
This is particularly useful when a persistence failure has caused
a created object not to be added to its parent container. #765
2016-05-23 14:07:09 -07:00
Even Stensberg
677b65d124 Optional indent removed
removed whitespace
2016-05-11 00:39:16 +02:00
Even Stensberg
31d31d7819 Removed indents
Removed unnecessary indentations
2016-05-11 00:14:52 +02:00
Even Stensberg
d32ef4bc3d Last indentation fix 2016-05-10 22:03:07 +02:00
Even Stensberg
b4faf8991d Indentation Fixes 2016-05-10 22:02:19 +02:00
Even Stensberg
0564759481 Swapped order of license
Swaped license order with name
2016-05-10 21:48:24 +02:00
Even Stensberg
24e391edf7 [Branding] Added Apache license to README.md
Added Apache license
2016-05-10 21:44:10 +02:00
Even Stensberg
cf295105d4 [Functionality] Log app instance to the console
Added logging statement, so the user knows when the app is up and running.
2016-05-10 21:06:04 +02:00
Victor Woeltjen
f16a107105 [Timeline] Inject resources into CSV action 2016-04-15 08:51:22 -07:00
Victor Woeltjen
f683ca44a2 [Timeline] Read resource utilizations during CSV export 2016-04-15 08:45:42 -07:00
Victor Woeltjen
546cde56a8 [Timeline] Expose internal resource utilization
...to allow this to be exported for CSV, #751
2016-04-15 08:32:34 -07:00
188 changed files with 5451 additions and 7836 deletions

285
API.md
View File

@@ -1,285 +0,0 @@
# Open MCT API
The Open MCT framework public api can be utilized by building the application
(`gulp install`) and then copying the file from `dist/main.js` to your
directory of choice.
Open MCT supports AMD, CommonJS, and loading via a script tag; it's easy to use
in your project. The [`openmct`]{@link module:openmct} module is exported
via AMD and CommonJS, and is also exposed as `openmct` in the global scope
if loaded via a script tag.
## Overview
Open MCT's goal is to allow you to browse, create, edit, and visualize all of
the domain knowledge you need on a daily basis.
To do this, the main building block provided by Open MCT is the _domain object_.
The temperature sensor on the starboard solar panel,
an overlay plot comparing the results of all temperature sensor,
the command dictionary for a spacecraft,
the individual commands in that dictionary, your "my documents" folder:
All of these things are domain objects.
Domain objects have Types, so a specific instrument temperature sensor is a
"Telemetry Point," and turning on a drill for a certain duration of time is
an "Activity". Types allow you to form an ontology of knowledge and provide
an abstraction for grouping, visualizing, and interpreting data.
And then we have Views. Views allow you to visualize domain objects. Views can
apply to specific domain objects; they may also apply to certain types of
domain objects, or they may apply to everything. Views are simply a method
of visualizing domain objects.
Regions allow you to specify what views are displayed for specific types of
domain objects in response to different user actions. For instance, you may
want to display a different view while editing, or you may want to update the
toolbar display when objects are selected. Regions allow you to map views to
specific user actions.
Domain objects can be mutated and persisted, developers can create custom
actions and apply them to domain objects, and many more things can be done.
For more information, read on!
## Running Open MCT
Once the [`openmct`](@link module:openmct) module has been loaded, you can
simply invoke [`start`]{@link module:openmct.MCT#start} to run Open MCT:
```
openmct.start();
```
Generally, however, you will want to configure Open MCT by adding plugins
before starting it. It is important to install plugins and configure Open MCT
_before_ calling [`start`]{@link module:openmct.MCT#start}; Open MCT is not
designed to be reconfigured once started.
## Configuring Open MCT
The [`openmct`]{@link module:openmct} module (more specifically, the
[`MCT`]{@link module:openmct.MCT} class, of which `openmct` is an instance)
exposes a variety of methods to allow the application to be configured,
extended, and customized before running.
Short examples follow; see the linked documentation for further details.
### Adding Domain Object Types
Custom types may be registered via
[`openmct.types`]{@link module:openmct.MCT#types}:
```
openmct.types.addType('my-type', new openmct.Type({
label: "My Type",
description: "This is a type that I added!"
});
```
### Adding Views
Custom views may be registered based on the region in the application
where they should appear:
* [`openmct.mainViews`]{@link module:openmct.MCT#mainViews} is a registry
of views of domain objects which should appear in the main viewing area.
* [`openmct.inspectors`]{@link module:openmct.MCT#inspectors} is a registry
of views of domain objects and/or active selections, which should appear in
the Inspector.
* [`openmct.toolbars`]{@link module:openmct.MCT#toolbars} is a registry
of views of domain objects and/or active selections, which should appear in
the toolbar area while editing.
* [`openmct.indicators`]{@link module:openmct.MCT#inspectors} is a registry
of views which should appear in the status area of the application.
Example:
```
openmct.mainViews.addProvider({
canView: function (domainObject) {
return domainObject.type === 'my-type';
},
view: function (domainObject) {
return new MyView(domainObject);
}
});
```
### Adding a Root-level Object
In many cases, you'd like a certain object (or a certain hierarchy of
objects) to be accessible from the top level of the application (the
tree on the left-hand side of Open MCT.) It is typical to expose a telemetry
dictionary as a hierarchy of telemetry-providing domain objects in this
fashion.
To do so, use the [`addRoot`]{@link module:openmct.ObjectAPI#addRoot} method
of the [object API]{@link module:openmct.ObjectAPI}:
```
openmct.objects.addRoot({
identifier: { key: "my-key", namespace: "my-namespace" }
name: "My Root-level Object",
type: "my-type"
});
```
You can also remove this root-level object via its identifier:
```
openmct.objects.removeRoot({ key: "my-key", namespace: "my-namespace" });
```
### Adding Composition Providers
The "composition" of a domain object is the list of objects it contains,
as shown (for example) in the tree for browsing. Open MCT provides a
default solution for composition, but there may be cases where you want
to provide the composition of a certain object (or type of object) dynamically.
For instance, you may want to populate a hierarchy under a custom root-level
object based on the contents of a telemetry dictionary.
To do this, you can add a new CompositionProvider:
```
openmct.composition.addProvider({
appliesTo: function (domainObject) {
return domainObject.type === 'my-type';
},
load: function (domainObject) {
return Promise.resolve(myDomainObjects);
}
});
```
### Adding Telemetry Providers
When connecting to a new telemetry source, you will want to register a new
[telemetry provider]{@link module:openmct.TelemetryAPI~TelemetryProvider}
with the [telemetry API]{@link module:openmct.TelemetryAPI#addProvider}:
```
openmct.telemetry.addProvider({
canProvideTelemetry: function (domainObject) {
return domainObject.type === 'my-type';
},
properties: function (domainObject) {
return [
{ key: 'value', name: "Temperature", units: "degC" },
{ key: 'time', name: "UTC" }
];
},
request: function (domainObject, options) {
var telemetryId = domainObject.myTelemetryId;
return myAdapter.request(telemetryId, options.start, options.end);
},
subscribe: function (domainObject, callback) {
var telemetryId = domainObject.myTelemetryId;
myAdapter.subscribe(telemetryId, callback);
return myAdapter.unsubscribe.bind(myAdapter, telemetryId, callback);
}
});
```
The implementations for `request` and `subscribe` can vary depending on the
nature of the endpoint which will provide telemetry. In the example above,
it is assumed that `myAdapter` contains the specific implementations
(HTTP requests, WebSocket connections, etc.) associated with some telemetry
source.
## Using Open MCT
When implementing new features, it is useful and sometimes necessary to
utilize functionality exposed by Open MCT.
### Retrieving Composition
To limit which objects are loaded at any given time, the composition of
a domain object must be requested asynchronously:
```
openmct.composition(myObject).load().then(function (childObjects) {
childObjects.forEach(doSomething);
});
```
### Support Common Gestures
Custom views may also want to support common gestures using the
[gesture API]{@link module:openmct.GestureAPI}. For instance, to make
a view (or part of a view) selectable:
```
openmct.gestures.selectable(myHtmlElement, myDomainObject);
```
### Working with Domain Objects
The [object API]{@link module:openmct.ObjectAPI} provides useful methods
for working with domain objects.
To make changes to a domain object, use the
[`mutate`]{@link module:openmct.ObjectAPI#mutate} method:
```
openmct.objects.mutate(myDomainObject, "name", "New name!");
```
Making modifications in this fashion allows other usages of the domain
object to remain up to date using the
[`observe`]{@link module:openmct.ObjectAPI#observe} method:
```
openmct.objects.observe(myDomainObject, "name", function (newName) {
myLabel.textContent = newName;
});
```
### Using Telemetry
Very often in Open MCT, you wish to work with telemetry data (for instance,
to display it in a custom visualization.)
### Synchronizing with the Time Conductor
Views which wish to remain synchronized with the state of Open MCT's
time conductor should utilize
[`openmct.conductor`]{@link module:openmct.TimeConductor}:
```
openmct.conductor.on('bounds', function (newBounds) {
requestTelemetry(newBounds.start, newBounds.end).then(displayTelemetry);
});
```
## Plugins
While you can register new features with Open MCT directly, it is generally
more useful to package these as a plugin. A plugin is a function that takes
[`openmct`]{@link module:openmct} as an argument, and performs configuration
upon `openmct` when invoked.
### Installing Plugins
To install plugins, use the [`install`]{@link module:openmct.MCT#install}
method:
```
openmct.install(myPlugin);
```
The plugin will be invoked to configure Open MCT before it is started.
### Writing Plugins
Plugins configure Open MCT, and should utilize the
[`openmct`]{@link module:openmct} module to do so, as summarized above in
"Configuring Open MCT" and "Using Open MCT" above.
### Distributing Plugins
Hosting or downloading plugins is outside of the scope of this documentation.
We recommend distributing plugins as UMD modules which export a single
function.

View File

@@ -1,4 +1,4 @@
# Open MCT
# Open MCT [![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
Open MCT is a web-based platform for mission operations user interface
software.
@@ -7,7 +7,7 @@ software.
A bundle is a group of software components (including source code, declared
as AMD modules, as well as resources such as images and HTML templates)
that are intended to be added or removed as a single unit. A plug-in for
that is intended to be added or removed as a single unit. A plug-in for
Open MCT will be expressed as a bundle; platform components are also
expressed as bundles.
@@ -133,6 +133,6 @@ documentation, may presume an understanding of these terms.
it, and it is thereafter considered the _navigated_ object (until the
user makes another such choice.)
* _space_: A name used to identify a persistence store. Interactions with
persistence with generally involve a `space` parameter in some form, to
persistence will generally involve a `space` parameter in some form, to
distinguish multiple persistence stores from one another (for cases
where there are multiple valid persistence locations available.)

8
app.js
View File

@@ -75,6 +75,8 @@
// Expose everything else as static files
app.use(express['static'](options.directory));
// Finally, open the HTTP server
app.listen(options.port);
}());
// Finally, open the HTTP server and log the instance to the console
app.listen(options.port, function() {
console.log('Open MCT application running at localhost:' + options.port)
});
}());

View File

@@ -20,7 +20,6 @@
"FileSaver.js": "^0.0.2",
"zepto": "^1.1.6",
"eventemitter3": "^1.2.0",
"lodash": "3.10.1",
"almond": "~0.3.2"
"d3": "~4.1.0"
}
}

View File

@@ -1,13 +0,0 @@
module.exports = {
handlers: {
processingComplete: function (e) {
e.doclets.forEach(function (doclet) {
var memberof = doclet.memberof || "";
var longname = doclet.longname || "";
doclet.ignore = longname !== 'module:openmct' &&
memberof.indexOf('module:openmct') !== 0;
});
}
}
};

View File

@@ -1,65 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Implementing a composition provider</title>
<script src="dist/main.js"></script>
</head>
<body>
<script>
var widgetParts = ['foo', 'bar', 'baz', 'bing', 'frobnak']
function fabricateName() {
return [
widgetParts[Math.floor(Math.random() * widgetParts.length)],
widgetParts[Math.floor(Math.random() * widgetParts.length)],
Math.floor(Math.random() * 1000)
].join('_');
}
MCT.type('example.widget-factory', new MCT.Type({
metadata: {
label: "Widget Factory",
glyph: "s",
description: "A factory for making widgets"
},
initialize: function (object) {
object.widgetCount = 5;
object.composition = [];
},
creatable: true,
form: [
{
name: "Widget Count",
control: "textfield",
key: "widgetCount",
property: "widgetCount",
required: true
}
]
}));
MCT.Composition.addProvider({
appliesTo: function (domainObject) {
return domainObject.type === 'example.widget-factory';
},
load: function (domainObject) {
var widgets = [];
while (widgets.length < domainObject.widgetCount) {
widgets.push({
name: fabricateName(),
key: {
namespace: 'widget-factory',
identifier: '' + widgets.length
}
});
}
return Promise.resolve(widgets);
}
});
MCT.run();
</script>
</body>
</html>

View File

@@ -1,144 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Implementing a Custom Type and View </title>
<script src="dist/main.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.1/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
</head>
<body>
<script type="text/babel">
// First, we're going to create the Todo List type, so that users can create
// todo lists.
MCT.type('example.todo', new MCT.Type({
metadata: {
label: "To-Do List",
glyph: "2",
description: "A list of things that need to be done."
},
initialize: function (object) {
object.tasks = [
{ description: "This is a task." }
];
},
creatable: true
}));
/*
Refresh the page, and you should be able to create new Todo Lists.
unfortunately, when you navigate to a Todo list, you see a blank page. let's
fix that by adding a main view for that todo list.
If you're wondering why this is commented out, well, it's because we'll
write a new version later.
*/
var Task = React.createClass({
render: function() {
return (
<li>
<input type="checkbox"
checked={this.props.checked}/>
<span>{this.props.description}</span>
</li>
);
}
});
var TaskList = React.createClass({
render: function () {
var taskNodes = this.props.tasks.map(function(task) {
return (
<Task checked={task.checked}
description={task.description}/>
);
});
return (
<ul>
{taskNodes}
</ul>
);
}
});
MCT.view(MCT.regions.main, {
canView: function (domainObject) {
return domainObject.type === 'example.todo';
},
view: function (domainObject) {
var mutableObject = MCT.Objects.getMutable(domainObject);
return {
show: function (container) {
ReactDOM.render(
<TaskList tasks={domainObject.tasks}/>,
container
);
mutableObject.on('tasks', function (tasks) {
ReactDOM.render(
<TaskList tasks={tasks}/>,
container
);
});
}
};
}
});
/*
Refresh the page and you should see a todo list with checkboxes! Now let's
Allow you to add tasks by mutating the object. We'll add a toolbar view to
do this.
*/
var TaskToolbar = React.createClass({
render: function () {
return (
<button onClick={this.props.addTask}>Add Task</button>
);
}
});
MCT.view(MCT.regions.toolbar, {
canView: function (domainObject) {
return domainObject.type === 'example.todo';
},
view: function (domainObject) {
var mutableObject = MCT.Objects.getMutable(domainObject);
function addTask(event) {
var description = prompt('Task description');
var tasks = mutableObject.get('tasks');
tasks.push({
description: description,
complete: false
});
mutableObject.set('tasks', tasks);
}
return {
show: function (container) {
ReactDOM.render(
<TaskToolbar addTask={addTask}/>,
container
);
},
}
}
});
/*
Refresh the page, edit the todo list, and you'll have a button that allows
you to add tasks! Unfortunately, new tasks don't show in the list. Why?
Well, if your view should update on mutation, you need to set up the correct
listeners. Let's update the TodoView we made earlier:
*/
MCT.run();
</script>
</body>
</html>

View File

@@ -1,160 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Implementing a Custom Type and View </title>
<script src="dist/main.js"></script>
</head>
<body>
<script>
// First, we're going to create the Todo List type, so that users can create
// todo lists.
MCT.type('example.todo', new MCT.Type({
metadata: {
label: "To-Do List",
glyph: "2",
description: "A list of things that need to be done."
},
initialize: function (object) {
object.tasks = [
{ description: "This is a task." }
];
},
creatable: true
}));
/*
Refresh the page, and you should be able to create new Todo Lists.
unfortunately, when you navigate to a Todo list, you see a blank page. let's
fix that by adding a main view for that todo list.
If you're wondering why this is commented out, well, it's because we'll
write a new version later.
*/
// MCT.view(MCT.regions.main, {
// canView: function (domainObject) {
// return domainObject.type === 'example.todo';
// },
// view: function (domainObject) {
// function renderTask(task) {
// return [
// '<li>',
// '<input type="checkbox"' + (task.complete ? ' checked="true"' : '') + '>',
// '<span>' + task.description + '</span>',
// '</li>'
// ].join('');
// };
//
// function renderTaskList() {
// return [
// '<ul>',
// domainObject.tasks.map(renderTask).join(''),
// '</ul>'
// ].join('');
// };
//
// return {
// show: function (container) {
// container.innerHTML = renderTaskList();
// }
// };
// }
// });
/*
Refresh the page and you should see a todo list with checkboxes! Now let's
Allow you to add tasks by mutating the object. We'll add a toolbar view to
do this.
*/
MCT.view(MCT.regions.toolbar, {
canView: function (domainObject) {
return domainObject.type === 'example.todo';
},
view: function (domainObject) {
var mutableObject = MCT.Objects.getMutable(domainObject);
function addTask(event) {
var description = prompt('Task description');
var tasks = mutableObject.get('tasks');
tasks.push({
description: description,
complete: false
});
mutableObject.set('tasks', tasks);
}
return {
show: function (container) {
container.addEventListener('click', addTask);
container.innerHTML = '<button>Add Task</button>';
},
destroy: function (container) {
container.removeEventListener('click', addTask);
}
}
}
});
/*
Refresh the page, edit the todo list, and you'll have a button that allows
you to add tasks! Unfortunately, new tasks don't show in the list. Why?
Well, if your view should update on mutation, you need to set up the correct
listeners. Let's update the TodoView we made earlier:
*/
MCT.view(MCT.regions.main, {
canView: function(domainObject) {
return domainObject.type === 'example.todo'
},
view: function (domainObject) {
var mutableObject = MCT.Objects.getMutable(domainObject);
function renderTask(task) {
return [
'<li>',
'<input type="checkbox"' + (task.complete ? ' checked="true"' : '') + '>',
'<span>' + task.description + '</span>',
'</li>'
].join('');
}
function renderTaskList(tasks) {
return [
'<ul>',
tasks.map(renderTask).join(''),
'</ul>'
].join('');
}
function onCheckboxChange(event) {
var checkbox = event.target;
var taskEl = checkbox.parentNode;
var taskList = taskEl.parentNode;
var taskIndex = [].slice.apply(taskList.children).indexOf(taskEl);
mutableObject.set('tasks[' + taskIndex + '].complete', checkbox.checked);
}
return {
show: function (container) {
container.innerHTML = renderTaskList(domainObject.tasks);
mutableObject.on('tasks', function (tasks) {
container.innerHTML = renderTaskList(tasks);
});
container.addEventListener('change', onCheckboxChange);
},
destroy: function () {
mutableObject.stopListening();
}
};
}
});
MCT.run();
</script>
</body>
</html>

View File

@@ -36,7 +36,7 @@ define([
legacyRegistry
) {
"use strict";
legacyRegistry.register("example/msl-adapter", {
legacyRegistry.register("example/notifications", {
"name" : "Mars Science Laboratory Data Adapter",
"extensions" : {
"types": [

View File

@@ -21,33 +21,40 @@
*****************************************************************************/
/*global require,__dirname*/
var gulp = require('gulp'),
requirejsOptimize = require('gulp-requirejs-optimize'),
sourcemaps = require('gulp-sourcemaps'),
rename = require('gulp-rename'),
sass = require('gulp-sass'),
bourbon = require('node-bourbon'),
jshint = require('gulp-jshint'),
jscs = require('gulp-jscs'),
replace = require('gulp-replace-task'),
karma = require('karma'),
path = require('path'),
fs = require('fs'),
git = require('git-rev-sync'),
moment = require('moment'),
merge = require('merge-stream'),
project = require('./package.json'),
_ = require('lodash'),
paths = {
main: 'main.js',
dist: 'dist',
assets: 'dist/assets',
scss: ['./platform/**/*.scss', './example/**/*.scss'],
assets: [
'./{example,platform}/**/*.{css,css.map,png,svg,ico,woff,eot,ttf}'
],
scripts: [ 'main.js', 'platform/**/*.js', 'src/**/*.js' ],
specs: [ 'platform/**/*Spec.js', 'src/**/*Spec.js' ],
static: [
'index.html',
'platform/**/*',
'example/**/*',
'bower_components/**/*'
]
},
options = {
requirejsOptimize: {
name: 'bower_components/almond/almond.js',
include: paths.main.replace('.js', ''),
wrap: {
startFile: "src/start.frag",
endFile: "src/end.frag"
},
name: paths.main.replace(/\.js$/, ''),
mainConfigFile: paths.main,
wrapShim: true
},
@@ -56,6 +63,7 @@ var gulp = require('gulp'),
singleRun: true
},
sass: {
includePaths: bourbon.includePaths,
sourceComments: true
},
replace: {
@@ -69,8 +77,6 @@ var gulp = require('gulp'),
};
gulp.task('scripts', function () {
var requirejsOptimize = require('gulp-requirejs-optimize');
var replace = require('gulp-replace-task');
return gulp.src(paths.main)
.pipe(sourcemaps.init())
.pipe(requirejsOptimize(options.requirejsOptimize))
@@ -80,16 +86,10 @@ gulp.task('scripts', function () {
});
gulp.task('test', function (done) {
var karma = require('karma');
new karma.Server(options.karma, done).start();
});
gulp.task('stylesheets', function () {
var sass = require('gulp-sass');
var rename = require('gulp-rename');
var bourbon = require('node-bourbon');
options.sass.includePaths = bourbon.includePaths;
return gulp.src(paths.scss, {base: '.'})
.pipe(sourcemaps.init())
.pipe(sass(options.sass).on('error', sass.logError))
@@ -103,9 +103,6 @@ gulp.task('stylesheets', function () {
});
gulp.task('lint', function () {
var jshint = require('gulp-jshint');
var merge = require('merge-stream');
var nonspecs = paths.specs.map(function (glob) {
return "!" + glob;
}),
@@ -120,8 +117,6 @@ gulp.task('lint', function () {
});
gulp.task('checkstyle', function () {
var jscs = require('gulp-jscs');
return gulp.src(paths.scripts)
.pipe(jscs())
.pipe(jscs.reporter())
@@ -129,35 +124,18 @@ gulp.task('checkstyle', function () {
});
gulp.task('fixstyle', function () {
var jscs = require('gulp-jscs');
return gulp.src(paths.scripts, { base: '.' })
.pipe(jscs({ fix: true }))
.pipe(gulp.dest('.'));
});
gulp.task('assets', ['stylesheets'], function () {
return gulp.src(paths.assets)
gulp.task('static', ['stylesheets'], function () {
return gulp.src(paths.static, { base: '.' })
.pipe(gulp.dest(paths.dist));
});
gulp.task('watch', function () {
return gulp.watch(paths.scss, ['stylesheets', 'assets']);
});
gulp.task('api', function () {
var jsdoc2md = require('gulp-jsdoc-to-markdown');
var concat = require('gulp-concat');
var markdown = require('gulp-markdown');
return gulp.src('src/**/*.js')
.pipe(concat('api.md'))
.pipe(jsdoc2md(require('./jsdoc.json')))
.pipe(markdown())
.pipe(gulp.dest(paths.dist));
});
gulp.task('api-watch', function () {
return gulp.watch('src/**/*.js', ['api']);
gulp.watch(paths.scss, ['stylesheets']);
});
gulp.task('serve', function () {
@@ -165,9 +143,9 @@ gulp.task('serve', function () {
var app = require('./app.js');
});
gulp.task('develop', ['serve', 'install', 'watch']);
gulp.task('develop', ['serve', 'stylesheets', 'watch']);
gulp.task('install', [ 'assets', 'scripts' ]);
gulp.task('install', [ 'static', 'scripts' ]);
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);

View File

@@ -31,14 +31,10 @@
<script type="text/javascript">
require(['main'], function (mct) {
require([
'./tutorials/todo/todo',
'./example/imagery/bundle',
'./example/eventGenerator/bundle',
'./example/generator/bundle'
], function (todoPlugin) {
mct.install(todoPlugin);
mct.run();
})
], mct.run.bind(mct));
});
</script>
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
@@ -52,5 +48,7 @@
<div class="l-splash-holder s-splash-holder">
<div class="l-splash s-splash"></div>
</div>
<div ng-view></div>
</body>
</html>

View File

@@ -1,13 +1,12 @@
{
"source": {
"include": [
"src/"
"platform/"
],
"includePattern": "src/.+\\.js$",
"includePattern": "platform/.+\\.js$",
"excludePattern": ".+\\Spec\\.js$|lib/.+"
},
"plugins": [
"plugins/markdown",
"build/jsdoc/plugins/mct-only"
"plugins/markdown"
]
}

65
main.js
View File

@@ -36,7 +36,7 @@ requirejs.config({
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
"zepto": "bower_components/zepto/zepto.min",
"lodash": "bower_components/lodash/lodash"
"d3": "bower_components/d3/d3.min"
},
"shim": {
"angular": {
@@ -57,31 +57,56 @@ requirejs.config({
"zepto": {
"exports": "Zepto"
},
"lodash": {
"exports": "lodash"
"d3": {
"exports": "d3"
}
}
});
define([
'./platform/framework/src/Main',
'./src/defaultRegistry',
'./src/MCT'
], function (Main, defaultRegistry, MCT) {
var mct = new MCT();
'legacyRegistry',
mct.legacyRegistry = defaultRegistry;
mct.run = function (domElement) {
if (!domElement) { domElement = document.body; }
var appDiv = document.createElement('div');
appDiv.setAttribute('ng-view', '');
appDiv.className = 'user-environ';
domElement.appendChild(appDiv);
mct.start();
'./platform/framework/bundle',
'./platform/core/bundle',
'./platform/representation/bundle',
'./platform/commonUI/about/bundle',
'./platform/commonUI/browse/bundle',
'./platform/commonUI/edit/bundle',
'./platform/commonUI/dialog/bundle',
'./platform/commonUI/formats/bundle',
'./platform/commonUI/general/bundle',
'./platform/commonUI/inspect/bundle',
'./platform/commonUI/mobile/bundle',
'./platform/commonUI/themes/espresso/bundle',
'./platform/commonUI/notification/bundle',
'./platform/containment/bundle',
'./platform/execution/bundle',
'./platform/exporters/bundle',
'./platform/telemetry/bundle',
'./platform/features/clock/bundle',
'./platform/features/imagery/bundle',
'./platform/features/layout/bundle',
'./platform/features/pages/bundle',
'./platform/features/plot/bundle',
'./platform/features/timeline/bundle',
'./platform/features/conductor-v2/bundle',
'./platform/features/table/bundle',
'./platform/forms/bundle',
'./platform/identity/bundle',
'./platform/persistence/aggregator/bundle',
'./platform/persistence/local/bundle',
'./platform/persistence/queue/bundle',
'./platform/policy/bundle',
'./platform/entanglement/bundle',
'./platform/search/bundle',
'./platform/status/bundle',
'./platform/commonUI/regions/bundle'
], function (Main, legacyRegistry) {
return {
legacyRegistry: legacyRegistry,
run: function () {
return new Main().run(legacyRegistry);
}
};
mct.on('start', function () {
return new Main().run(defaultRegistry);
});
return mct;
});

View File

@@ -1,64 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Groot Tutorial</title>
<script src="dist/main.js"></script>
</head>
<body>
<script>
// First, we need a source of objects, so we're going to define a few
// objects that we wish to expose. The first object is a folder which
// contains the other objects.
var GROOT_ROOT = {
name: 'I am groot',
type: 'folder',
composition: [
{
namespace: 'groot',
identifier: 'arms'
},
{
namespace: 'groot',
identifier: 'legs'
},
{
namespace: 'groot',
identifier: 'torso'
}
]
};
// Now, we will define an object provider. This will allow us to fetch the
// GROOT_ROOT object, plus any other objects in the `groot` namespace.
// For more info, see the Object API documentation.
MCT.Objects.addProvider('groot', {
// we'll only define a get function, objects from this provider will
// not be mutable.
get: function (key) {
if (key.identifier === 'groot') {
return Promise.resolve(GROOT_ROOT);
}
return Promise.resolve({
name: 'Groot\'s ' + key.identifier
});
}
});
// Finally, we need to add a "ROOT." This is an identifier for an object
// that Open MCT will load at run time and show at the top-level of the
// navigation tree.
MCT.Objects.addRoot({
namespace: 'groot',
identifier: 'groot'
});
MCT.run();
</script>
</body>
</html>

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "0.10.2-SNAPSHOT",
"version": "0.11.0-SNAPSHOT",
"description": "The Open MCT core platform",
"dependencies": {
"express": "^4.13.1",
@@ -12,11 +12,8 @@
"git-rev-sync": "^1.4.0",
"glob": ">= 3.0.0",
"gulp": "^3.9.0",
"gulp-concat": "^2.6.0",
"gulp-jscs": "^3.0.2",
"gulp-jsdoc-to-markdown": "^1.2.2",
"gulp-jshint": "^2.0.0",
"gulp-markdown": "^1.2.0",
"gulp-rename": "^1.2.2",
"gulp-replace-task": "^0.11.0",
"gulp-requirejs-optimize": "^0.3.1",
@@ -50,7 +47,7 @@
"test": "karma start --single-run",
"jshint": "jshint platform example",
"watch": "karma start",
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d target/docs/api",
"jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api",
"otherdoc": "node docs/gendocs.js --in docs/src --out target/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"

View File

@@ -27,6 +27,7 @@ define([
"./src/MenuArrowController",
"./src/navigation/NavigationService",
"./src/navigation/NavigateAction",
"./src/navigation/OrphanNavigationHandler",
"./src/windowing/NewTabAction",
"./src/windowing/FullscreenAction",
"./src/windowing/WindowTitler",
@@ -39,7 +40,6 @@ define([
"text!./res/templates/items/items.html",
"text!./res/templates/browse/object-properties.html",
"text!./res/templates/browse/inspector-region.html",
"text!./res/templates/view-object.html",
'legacyRegistry'
], function (
BrowseController,
@@ -48,6 +48,7 @@ define([
MenuArrowController,
NavigationService,
NavigateAction,
OrphanNavigationHandler,
NewTabAction,
FullscreenAction,
WindowTitler,
@@ -60,7 +61,6 @@ define([
itemsTemplate,
objectPropertiesTemplate,
inspectorRegionTemplate,
viewObjectTemplate,
legacyRegistry
) {
@@ -93,11 +93,9 @@ define([
"$scope",
"$route",
"$location",
"$window",
"objectService",
"navigationService",
"urlService",
"policyService",
"DEFAULT_PATH"
]
},
@@ -131,7 +129,7 @@ define([
"representations": [
{
"key": "view-object",
"template": viewObjectTemplate
"templateUrl": "templates/view-object.html"
},
{
"key": "browse-object",
@@ -201,7 +199,9 @@ define([
"implementation": NavigateAction,
"depends": [
"navigationService",
"$q"
"$q",
"policyService",
"$window"
]
},
{
@@ -255,6 +255,14 @@ define([
"$rootScope",
"$document"
]
},
{
"implementation": OrphanNavigationHandler,
"depends": [
"throttle",
"topic",
"navigationService"
]
}
],
"licenses": [

View File

@@ -43,7 +43,7 @@
</mct-representation>
</div>
</div>
<div class="holder l-flex-col flex-elem grows l-object-wrapper">
<div class="holder l-flex-col flex-elem grows l-object-wrapper l-controls-visible l-time-controller-visible">
<div class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
<!-- Toolbar and Save/Cancel buttons -->
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
@@ -59,4 +59,9 @@
</mct-representation>
</div>
</div>
<!-- put time conductor in here? -->
<mct-representation mct-object="domainObject"
key="'time-conductor'"
class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder">
</mct-representation>
</div>

View File

@@ -44,11 +44,9 @@ define(
$scope,
$route,
$location,
$window,
objectService,
navigationService,
urlService,
policyService,
defaultPath
) {
var path = [ROOT_ID].concat(
@@ -75,25 +73,10 @@ define(
}
// Callback for updating the in-scope reference to the object
// that is currently navigated-to.
function setNavigation(domainObject) {
var navigationAllowed = true;
if (domainObject === $scope.navigatedObject) {
//do nothing;
return;
}
policyService.allow("navigation", $scope.navigatedObject, domainObject, function (message) {
navigationAllowed = $window.confirm(message + "\r\n\r\n" +
" Are you sure you want to continue?");
});
function setScopeObjects(domainObject, navigationAllowed) {
if (navigationAllowed) {
$scope.navigatedObject = domainObject;
$scope.treeModel.selectedObject = domainObject;
navigationService.setNavigation(domainObject);
updateRoute(domainObject);
} else {
//If navigation was unsuccessful (ie. blocked), reset
@@ -103,6 +86,20 @@ define(
}
}
// Callback for updating the in-scope reference to the object
// that is currently navigated-to.
function setNavigation(domainObject) {
if (domainObject === $scope.navigatedObject) {
//do nothing;
return;
}
if (domainObject) {
domainObject.getCapability("action").perform("navigate").then(setScopeObjects.bind(undefined, domainObject));
} else {
setScopeObjects(domainObject, true);
}
}
function navigateTo(domainObject) {
// Check if an object has been navigated-to already...

View File

@@ -33,10 +33,12 @@ define(
* @constructor
* @implements {Action}
*/
function NavigateAction(navigationService, $q, context) {
function NavigateAction(navigationService, $q, policyService, $window, context) {
this.domainObject = context.domainObject;
this.$q = $q;
this.navigationService = navigationService;
this.policyService = policyService;
this.$window = $window;
}
/**
@@ -45,9 +47,20 @@ define(
* navigation has been updated
*/
NavigateAction.prototype.perform = function () {
var self = this,
navigationAllowed = true;
function allow() {
self.policyService.allow("navigation", self.navigationService.getNavigation(), self.domainObject, function (message) {
navigationAllowed = self.$window.confirm(message + "\r\n\r\n" +
" Are you sure you want to continue?");
});
return navigationAllowed;
}
// Set navigation, and wrap like a promise
return this.$q.when(
this.navigationService.setNavigation(this.domainObject)
allow() && this.navigationService.setNavigation(this.domainObject)
);
};

View File

@@ -0,0 +1,75 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* Navigates away from orphan objects whenever they are detected.
*
* An orphan object is an object whose apparent parent does not
* actually contain it. This may occur in certain circumstances, such
* as when persistence succeeds for a newly-created object but fails
* for its parent.
*
* @param throttle the `throttle` service
* @param topic the `topic` service
* @param navigationService the `navigationService`
* @constructor
*/
function OrphanNavigationHandler(throttle, topic, navigationService) {
var throttledCheckNavigation;
function getParent(domainObject) {
var context = domainObject.getCapability('context');
return context.getParent();
}
function isOrphan(domainObject) {
var parent = getParent(domainObject),
composition = parent.getModel().composition,
id = domainObject.getId();
return !composition || (composition.indexOf(id) === -1);
}
function navigateToParent(domainObject) {
var parent = getParent(domainObject);
return parent.getCapability('action').perform('navigate');
}
function checkNavigation() {
var navigatedObject = navigationService.getNavigation();
if (navigatedObject.hasCapability('context') &&
isOrphan(navigatedObject)) {
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
navigateToParent(navigatedObject);
}
}
}
throttledCheckNavigation = throttle(checkNavigation);
navigationService.addListener(throttledCheckNavigation);
topic('mutation').listen(throttledCheckNavigation);
}
return OrphanNavigationHandler;
});

View File

@@ -37,9 +37,8 @@ define(
mockUrlService,
mockDomainObject,
mockNextObject,
mockWindow,
mockPolicyService,
testDefaultRoot,
mockActionCapability,
controller;
function mockPromise(value) {
@@ -55,25 +54,14 @@ define(
mockScope,
mockRoute,
mockLocation,
mockWindow,
mockObjectService,
mockNavigationService,
mockUrlService,
mockPolicyService,
testDefaultRoot
);
}
beforeEach(function () {
mockWindow = jasmine.createSpyObj('$window', [
"confirm"
]);
mockWindow.confirm.andReturn(true);
mockPolicyService = jasmine.createSpyObj('policyService', [
'allow'
]);
testDefaultRoot = "some-root-level-domain-object";
mockScope = jasmine.createSpyObj(
@@ -128,6 +116,8 @@ define(
mockNextObject.getId.andReturn("next");
mockDomainObject.getId.andReturn(testDefaultRoot);
mockActionCapability = jasmine.createSpyObj('actionCapability', ['perform']);
instantiateController();
});
@@ -211,8 +201,13 @@ define(
mockContext.getPath.andReturn(
[mockRootObject, mockDomainObject, mockNextObject]
);
//Return true from navigate action
mockActionCapability.perform.andReturn(mockPromise(true));
mockNextObject.getCapability.andCallFake(function (c) {
return c === 'context' && mockContext;
return (c === 'context' && mockContext) ||
(c === 'action' && mockActionCapability);
});
mockScope.$on.andReturn(mockUnlisten);
// Provide a navigation change
@@ -225,6 +220,7 @@ define(
mockLocation.path.andReturn("/browse/");
mockNavigationService.setNavigation.andReturn(true);
mockActionCapability.perform.andReturn(mockPromise(true));
// Exercise the Angular workaround
mockNavigationService.addListener.mostRecentCall.args[0]();
@@ -243,6 +239,9 @@ define(
mockScope.navigatedObject = mockDomainObject;
mockNavigationService.setNavigation.andReturn(true);
mockActionCapability.perform.andReturn(mockPromise(true));
mockNextObject.getCapability.andReturn(mockActionCapability);
//Simulate a change in selected tree object
mockScope.treeModel = {selectedObject: mockDomainObject};
mockScope.$watch.mostRecentCall.args[1](mockNextObject);
@@ -254,11 +253,10 @@ define(
it("after failed navigation event resets the selected tree" +
" object", function () {
mockScope.navigatedObject = mockDomainObject;
mockWindow.confirm.andReturn(false);
mockPolicyService.allow.andCallFake(function (category, object, context, callback) {
callback("unsaved changes");
return false;
});
//Return false from navigation action
mockActionCapability.perform.andReturn(mockPromise(false));
mockNextObject.getCapability.andReturn(mockActionCapability);
//Simulate a change in selected tree object
mockScope.treeModel = {selectedObject: mockDomainObject};

View File

@@ -31,6 +31,8 @@ define(
var mockNavigationService,
mockQ,
mockDomainObject,
mockPolicyService,
mockWindow,
action;
function mockPromise(value) {
@@ -44,25 +46,70 @@ define(
beforeEach(function () {
mockNavigationService = jasmine.createSpyObj(
"navigationService",
["setNavigation"]
[
"setNavigation",
"getNavigation"
]
);
mockNavigationService.getNavigation.andReturn({});
mockQ = { when: mockPromise };
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getId", "getModel", "getCapability"]
);
mockPolicyService = jasmine.createSpyObj("policyService",
[
"allow"
]);
mockWindow = jasmine.createSpyObj("$window",
[
"confirm"
]);
action = new NavigateAction(
mockNavigationService,
mockQ,
mockPolicyService,
mockWindow,
{ domainObject: mockDomainObject }
);
});
it("invokes the navigate service when performed", function () {
it("invokes the policy service to determine if navigation" +
" allowed", function () {
action.perform();
expect(mockNavigationService.setNavigation)
.toHaveBeenCalledWith(mockDomainObject);
expect(mockPolicyService.allow)
.toHaveBeenCalledWith("navigation", jasmine.any(Object), jasmine.any(Object), jasmine.any(Function));
});
it("prompts user if policy rejection", function () {
action.perform();
expect(mockPolicyService.allow).toHaveBeenCalled();
mockPolicyService.allow.mostRecentCall.args[3]();
expect(mockWindow.confirm).toHaveBeenCalled();
});
describe("shows a prompt", function () {
beforeEach(function () {
// Ensure the allow callback is called synchronously
mockPolicyService.allow.andCallFake(function () {
return arguments[3]();
});
});
it("does not navigate on prompt rejection", function () {
mockWindow.confirm.andReturn(false);
action.perform();
expect(mockNavigationService.setNavigation)
.not.toHaveBeenCalled();
});
it("does navigate on prompt acceptance", function () {
mockWindow.confirm.andReturn(true);
action.perform();
expect(mockNavigationService.setNavigation)
.toHaveBeenCalled();
});
});
it("is only applicable when a domain object is in context", function () {

View File

@@ -0,0 +1,180 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'../../src/navigation/OrphanNavigationHandler'
], function (OrphanNavigationHandler) {
describe("OrphanNavigationHandler", function () {
var mockTopic,
mockThrottle,
mockMutationTopic,
mockNavigationService,
mockDomainObject,
mockParentObject,
mockContext,
mockActionCapability,
mockEditor,
testParentModel,
testId,
mockThrottledFns;
beforeEach(function () {
testId = 'some-identifier';
mockThrottledFns = [];
testParentModel = {};
mockTopic = jasmine.createSpy('topic');
mockThrottle = jasmine.createSpy('throttle');
mockNavigationService = jasmine.createSpyObj('navigationService', [
'getNavigation',
'addListener'
]);
mockMutationTopic = jasmine.createSpyObj('mutationTopic', [
'listen'
]);
mockDomainObject = jasmine.createSpyObj('domainObject', [
'getId',
'getCapability',
'getModel',
'hasCapability'
]);
mockParentObject = jasmine.createSpyObj('domainObject', [
'getId',
'getCapability',
'getModel',
'hasCapability'
]);
mockContext = jasmine.createSpyObj('context', ['getParent']);
mockActionCapability = jasmine.createSpyObj('action', ['perform']);
mockEditor = jasmine.createSpyObj('editor', ['isEditContextRoot']);
mockThrottle.andCallFake(function (fn) {
var mockThrottledFn =
jasmine.createSpy('throttled-' + mockThrottledFns.length);
mockThrottledFn.andCallFake(fn);
mockThrottledFns.push(mockThrottledFn);
return mockThrottledFn;
});
mockTopic.andCallFake(function (k) {
return k === 'mutation' && mockMutationTopic;
});
mockDomainObject.getId.andReturn(testId);
mockDomainObject.getCapability.andCallFake(function (c) {
return {
context: mockContext,
editor: mockEditor
}[c];
});
mockDomainObject.hasCapability.andCallFake(function (c) {
return !!mockDomainObject.getCapability(c);
});
mockParentObject.getModel.andReturn(testParentModel);
mockParentObject.getCapability.andCallFake(function (c) {
return {
action: mockActionCapability
}[c];
});
mockContext.getParent.andReturn(mockParentObject);
mockNavigationService.getNavigation.andReturn(mockDomainObject);
mockEditor.isEditContextRoot.andReturn(false);
return new OrphanNavigationHandler(
mockThrottle,
mockTopic,
mockNavigationService
);
});
it("listens for mutation with a throttled function", function () {
expect(mockMutationTopic.listen)
.toHaveBeenCalledWith(jasmine.any(Function));
expect(mockThrottledFns.indexOf(
mockMutationTopic.listen.mostRecentCall.args[0]
)).not.toEqual(-1);
});
it("listens for navigation changes with a throttled function", function () {
expect(mockNavigationService.addListener)
.toHaveBeenCalledWith(jasmine.any(Function));
expect(mockThrottledFns.indexOf(
mockNavigationService.addListener.mostRecentCall.args[0]
)).not.toEqual(-1);
});
[false, true].forEach(function (isOrphan) {
var prefix = isOrphan ? "" : "non-";
describe("for " + prefix + "orphan objects", function () {
beforeEach(function () {
testParentModel.composition = isOrphan ? [] : [testId];
});
[false, true].forEach(function (isEditRoot) {
var caseName = isEditRoot ?
"that are being edited" : "that are not being edited";
function itNavigatesAsExpected() {
if (isOrphan && !isEditRoot) {
it("navigates to the parent", function () {
expect(mockActionCapability.perform)
.toHaveBeenCalledWith('navigate');
});
} else {
it("does nothing", function () {
expect(mockActionCapability.perform)
.not.toHaveBeenCalled();
});
}
}
describe(caseName, function () {
beforeEach(function () {
mockEditor.isEditContextRoot.andReturn(isEditRoot);
});
describe("when navigation changes", function () {
beforeEach(function () {
mockNavigationService.addListener.mostRecentCall
.args[0](mockDomainObject);
});
itNavigatesAsExpected();
});
describe("when mutation occurs", function () {
beforeEach(function () {
mockMutationTopic.listen.mostRecentCall
.args[0](mockParentObject);
});
itNavigatesAsExpected();
});
});
});
});
});
});
});

View File

@@ -170,7 +170,7 @@ define([
"navigationService",
"$log"
],
"description": "Edit this object.",
"description": "Edit",
"category": "view-control",
"glyph": "p"
},
@@ -206,7 +206,9 @@ define([
"implementation": SaveAction,
"name": "Save",
"description": "Save changes made to these objects.",
"depends": [],
"depends": [
"dialogService"
],
"priority": "mandatory"
},
{

View File

@@ -36,14 +36,14 @@
</ul>
</div>
<div class="pane right menu-item-description">
<div class="desc-area ui-symbol icon type-icon">
<div class="pane right menu-item-description l-flex-col">
<div class="desc-area flex-elem holder ui-symbol icon type-icon">
{{representation.activeMetadata.glyph}}
</div>
<div class="desc-area title">
<div class="desc-area flex-elem holder title">
{{representation.activeMetadata.name}}
</div>
<div class="desc-area description">
<div class="desc-area flex-elem holder description">
{{representation.activeMetadata.description}}
</div>
</div>

View File

@@ -50,18 +50,24 @@ define(
//If the object existed already, navigate to refresh view
// with previous object state.
if (domainObject.getModel().persisted) {
domainObject.getCapability("action").perform("navigate");
return domainObject.getCapability("action").perform("navigate");
} else {
//If the object was new, and user has cancelled, then
//navigate back to parent because nothing to show.
domainObject.getCapability("location").getOriginal().then(function (original) {
return domainObject.getCapability("location").getOriginal().then(function (original) {
parent = original.getCapability("context").getParent();
parent.getCapability("action").perform("navigate");
});
}
}
return this.domainObject.getCapability("editor").cancel()
.then(returnToBrowse);
function cancel(allowed) {
return allowed && domainObject.getCapability("editor").cancel();
}
//Do navigation first in order to trigger unsaved changes dialog
return returnToBrowse()
.then(cancel);
};
/**

View File

@@ -21,8 +21,8 @@
*****************************************************************************/
define(
[],
function () {
['./SaveInProgressDialog'],
function (SaveInProgressDialog) {
/**
* The "Save" action; the action triggered by clicking Save from
@@ -33,9 +33,11 @@ define(
* @memberof platform/commonUI/edit
*/
function SaveAction(
dialogService,
context
) {
this.domainObject = (context || {}).domainObject;
this.dialogService = dialogService;
}
/**
@@ -46,7 +48,8 @@ define(
* @memberof platform/commonUI/edit.SaveAction#
*/
SaveAction.prototype.perform = function () {
var domainObject = this.domainObject;
var domainObject = this.domainObject,
dialog = new SaveInProgressDialog(this.dialogService);
function resolveWith(object) {
return function () {
@@ -72,8 +75,17 @@ define(
return object;
}
//return doSave().then(returnToBrowse);
return doSave().then(returnToBrowse);
function hideBlockingDialog(object) {
dialog.hide();
return object;
}
dialog.show();
return doSave()
.then(hideBlockingDialog)
.then(returnToBrowse)
.catch(hideBlockingDialog);
};
/**

View File

@@ -21,9 +21,14 @@
*****************************************************************************/
define(
['../creation/CreateWizard'],
function (CreateWizard) {
define([
'../creation/CreateWizard',
'./SaveInProgressDialog'
],
function (
CreateWizard,
SaveInProgressDialog
) {
/**
* The "Save" action; the action triggered by clicking Save from
@@ -105,7 +110,8 @@ define(
SaveAsAction.prototype.save = function () {
var self = this,
domainObject = this.domainObject,
copyService = this.copyService;
copyService = this.copyService,
dialog = new SaveInProgressDialog(this.dialogService);
function doWizardSave(parent) {
var wizard = self.createWizard(parent);
@@ -116,6 +122,16 @@ define(
).then(wizard.populateObjectFromInput.bind(wizard));
}
function showBlockingDialog(object) {
dialog.show();
return object;
}
function hideBlockingDialog(object) {
dialog.hide();
return object;
}
function fetchObject(objectId) {
return self.getObjectService().getObjects([objectId]).then(function (objects) {
return objects[objectId];
@@ -140,14 +156,22 @@ define(
.then(resolveWith(clonedObject));
}
function onFailure() {
hideBlockingDialog();
return false;
}
return getParent(domainObject)
.then(doWizardSave)
.then(showBlockingDialog)
.then(getParent)
.then(cloneIntoParent)
.then(commitEditingAfterClone)
.catch(resolveWith(false));
.then(hideBlockingDialog)
.catch(onFailure);
};
/**
* Check if this action is applicable in a given context.
* This will ensure that a domain object is present in the context,

View File

@@ -0,0 +1,20 @@
define([], function () {
function SaveInProgressDialog(dialogService) {
this.dialogService = dialogService;
}
SaveInProgressDialog.prototype.show = function () {
this.dialogService.showBlockingMessage({
title: "Saving...",
hint: "Do not navigate away from this page or close this browser tab while this message is displayed.",
unknownProgress: true,
severity: "info"
});
};
SaveInProgressDialog.prototype.hide = function () {
this.dialogService.dismiss();
};
return SaveInProgressDialog;
});

View File

@@ -67,10 +67,17 @@ define(
}
function onCancel() {
return self.persistenceCapability.refresh().then(function (result) {
if (self.domainObject.getModel().persisted !== undefined) {
//Fetch clean model from persistence
return self.persistenceCapability.refresh().then(function (result) {
self.persistPending = false;
return result;
});
} else {
self.persistPending = false;
return result;
});
//Model is undefined in persistence, so return undefined.
return self.$q.when(undefined);
}
}
if (this.transactionService.isActive()) {

View File

@@ -24,12 +24,11 @@ define(
["../../src/actions/CancelAction"],
function (CancelAction) {
//TODO: Disabled for NEM Beta
xdescribe("The Cancel action", function () {
var mockLocation,
mockDomainObject,
mockEditorCapability,
mockUrlService,
describe("The Cancel action", function () {
var mockDomainObject,
mockParentObject,
capabilities = {},
parentCapabilities = {},
actionContext,
action;
@@ -42,61 +41,114 @@ define(
}
beforeEach(function () {
mockLocation = jasmine.createSpyObj(
"$location",
["path"]
);
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getCapability", "hasCapability"]
[
"getCapability",
"hasCapability",
"getModel"
]
);
mockEditorCapability = jasmine.createSpyObj(
mockDomainObject.getModel.andReturn({});
mockParentObject = jasmine.createSpyObj(
"parentObject",
[
"getCapability"
]
);
mockParentObject.getCapability.andCallFake(function (name) {
return parentCapabilities[name];
});
capabilities.editor = jasmine.createSpyObj(
"editor",
["save", "cancel"]
["save", "cancel", "isEditContextRoot"]
);
mockUrlService = jasmine.createSpyObj(
"urlService",
["urlForLocation"]
capabilities.action = jasmine.createSpyObj(
"actionCapability",
[
"perform"
]
);
capabilities.location = jasmine.createSpyObj(
"locationCapability",
[
"getOriginal"
]
);
capabilities.location.getOriginal.andReturn(mockPromise(mockDomainObject));
capabilities.context = jasmine.createSpyObj(
"contextCapability",
[
"getParent"
]
);
capabilities.context.getParent.andReturn(mockParentObject);
parentCapabilities.action = jasmine.createSpyObj(
"actionCapability",
[
"perform"
]
);
actionContext = {
domainObject: mockDomainObject
};
mockDomainObject.hasCapability.andReturn(true);
mockDomainObject.getCapability.andReturn(mockEditorCapability);
mockEditorCapability.cancel.andReturn(mockPromise(true));
mockDomainObject.getCapability.andCallFake(function (name) {
return capabilities[name];
});
action = new CancelAction(mockLocation, mockUrlService, actionContext);
mockDomainObject.hasCapability.andCallFake(function (name) {
return !!capabilities[name];
});
capabilities.editor.cancel.andReturn(mockPromise(true));
action = new CancelAction(actionContext);
});
it("only applies to domain object with an editor capability", function () {
it("only applies to domain object that is being edited", function () {
capabilities.editor.isEditContextRoot.andReturn(true);
expect(CancelAction.appliesTo(actionContext)).toBeTruthy();
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
capabilities.editor.isEditContextRoot.andReturn(false);
expect(CancelAction.appliesTo(actionContext)).toBeFalsy();
mockDomainObject.hasCapability.andReturn(false);
mockDomainObject.getCapability.andReturn(undefined);
expect(CancelAction.appliesTo(actionContext)).toBeFalsy();
});
it("invokes the editor capability's save functionality when performed", function () {
// Verify precondition
expect(mockEditorCapability.cancel).not.toHaveBeenCalled();
it("invokes the editor capability's cancel functionality when" +
" performed", function () {
mockDomainObject.getModel.andReturn({persisted: 1});
//Return true from navigate action
capabilities.action.perform.andReturn(mockPromise(true));
action.perform();
// Should have called cancel
expect(mockEditorCapability.cancel).toHaveBeenCalled();
expect(capabilities.editor.cancel).toHaveBeenCalled();
// Definitely shouldn't call save!
expect(mockEditorCapability.save).not.toHaveBeenCalled();
expect(capabilities.editor.save).not.toHaveBeenCalled();
});
it("returns to browse when performed", function () {
it("navigates to object if existing using navigate action", function () {
mockDomainObject.getModel.andReturn({persisted: 1});
//Return true from navigate action
capabilities.action.perform.andReturn(mockPromise(true));
action.perform();
expect(mockLocation.path).toHaveBeenCalledWith(
mockUrlService.urlForLocation("browse", mockDomainObject)
);
expect(capabilities.action.perform).toHaveBeenCalledWith("navigate");
});
it("navigates to parent if new using navigate action", function () {
mockDomainObject.getModel.andReturn({persisted: undefined});
action.perform();
expect(parentCapabilities.action.perform).toHaveBeenCalledWith("navigate");
});
});
}

View File

@@ -28,6 +28,7 @@ define(
var mockDomainObject,
mockEditorCapability,
actionContext,
dialogService,
mockActionCapability,
capabilities = {},
action;
@@ -36,6 +37,9 @@ define(
return {
then: function (callback) {
return mockPromise(callback(value));
},
catch: function (callback) {
return mockPromise(callback(value));
}
};
}
@@ -64,6 +68,10 @@ define(
actionContext = {
domainObject: mockDomainObject
};
dialogService = jasmine.createSpyObj(
"dialogService",
["showBlockingMessage", "dismiss"]
);
mockDomainObject.hasCapability.andReturn(true);
mockDomainObject.getCapability.andCallFake(function (capability) {
@@ -73,8 +81,7 @@ define(
mockEditorCapability.save.andReturn(mockPromise(true));
mockEditorCapability.isEditContextRoot.andReturn(true);
action = new SaveAction(actionContext);
action = new SaveAction(dialogService, actionContext);
});
it("only applies to domain object with an editor capability", function () {
@@ -104,6 +111,19 @@ define(
expect(mockActionCapability.perform).toHaveBeenCalledWith("navigate");
});
it("shows a dialog while saving", function () {
mockEditorCapability.save.andReturn(new Promise(function () {}));
action.perform();
expect(dialogService.showBlockingMessage).toHaveBeenCalled();
expect(dialogService.dismiss).not.toHaveBeenCalled();
});
it("hides a dialog when saving is complete", function () {
action.perform();
expect(dialogService.showBlockingMessage).toHaveBeenCalled();
expect(dialogService.dismiss).toHaveBeenCalled();
});
});
}
);

View File

@@ -100,7 +100,9 @@ define(
mockDialogService = jasmine.createSpyObj(
"dialogService",
[
"getUserInput"
"getUserInput",
"showBlockingMessage",
"dismiss"
]
);
mockDialogService.getUserInput.andReturn(mockPromise(undefined));
@@ -169,6 +171,19 @@ define(
expect(mockDialogService.getUserInput).toHaveBeenCalled();
});
it("shows a blocking dialog while waiting for save", function () {
mockEditorCapability.save.andReturn(new Promise(function () {}));
action.perform();
expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
expect(mockDialogService.dismiss).not.toHaveBeenCalled();
});
it("hides the blocking dialog after saving", function () {
action.perform();
expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
expect(mockDialogService.dismiss).toHaveBeenCalled();
});
});
}
);

View File

@@ -57,6 +57,15 @@ define(
);
mockPersistence.persist.andReturn(fastPromise());
mockPersistence.refresh.andReturn(fastPromise());
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[
"getModel"
]
);
mockDomainObject.getModel.andReturn({persisted: 1});
capability = new TransactionalPersistenceCapability(mockQ, mockTransactionService, mockPersistence, mockDomainObject);
});
@@ -78,6 +87,20 @@ define(
expect(mockPersistence.refresh).toHaveBeenCalled();
});
it("if transaction is active, cancel call is queued that refreshes model when appropriate", function () {
mockTransactionService.isActive.andReturn(true);
capability.persist();
expect(mockTransactionService.addToTransaction).toHaveBeenCalled();
mockDomainObject.getModel.andReturn({});
mockTransactionService.addToTransaction.mostRecentCall.args[1]();
expect(mockPersistence.refresh).not.toHaveBeenCalled();
mockDomainObject.getModel.andReturn({persisted: 1});
mockTransactionService.addToTransaction.mostRecentCall.args[1]();
expect(mockPersistence.refresh).toHaveBeenCalled();
});
it("persist call is only added to transaction once", function () {
mockTransactionService.isActive.andReturn(true);
capability.persist();

View File

@@ -23,10 +23,12 @@
define([
"./src/FormatProvider",
"./src/UTCTimeFormat",
"./src/DurationFormat",
'legacyRegistry'
], function (
FormatProvider,
UTCTimeFormat,
DurationFormat,
legacyRegistry
) {
@@ -48,6 +50,10 @@ define([
{
"key": "utc",
"implementation": UTCTimeFormat
},
{
"key": "duration",
"implementation": DurationFormat
}
],
"constants": [

View File

@@ -19,32 +19,42 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define([
'./LegacyObjectAPIInterceptor',
'legacyRegistry'
'moment'
], function (
LegacyObjectAPIInterceptor,
legacyRegistry
moment
) {
legacyRegistry.register('src/api/objects', {
name: 'Object API',
description: 'The public Objects API',
extensions: {
components: [
{
provides: "objectService",
type: "decorator",
priority: "mandatory",
implementation: LegacyObjectAPIInterceptor,
depends: [
"roots[]",
"instantiate",
"topic"
]
}
]
}
});
var DATE_FORMAT = "HH:mm:ss",
DATE_FORMATS = [
DATE_FORMAT
];
/**
* Formatter for UTC timestamps. Interprets numeric values as
* milliseconds since the start of 1970. Displays only the utc time. Can
* be used, with care, for specifying time intervals.
*
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
function DurationFormat() {
}
DurationFormat.prototype.format = function (value) {
return moment.utc(value).format(DATE_FORMAT);
};
DurationFormat.prototype.parse = function (text) {
return moment.duration(text).asMilliseconds();
};
DurationFormat.prototype.validate = function (text) {
return moment.utc(text, DATE_FORMATS).isValid();
};
return DurationFormat;
});

View File

@@ -48,7 +48,6 @@ define([
"./src/directives/MCTSplitPane",
"./src/directives/MCTSplitter",
"./src/directives/MCTTree",
"./src/filters/ReverseFilter.js",
"text!./res/templates/bottombar.html",
"text!./res/templates/controls/action-button.html",
"text!./res/templates/controls/input-filter.html",
@@ -97,7 +96,6 @@ define([
MCTSplitPane,
MCTSplitter,
MCTTree,
ReverseFilter,
bottombarTemplate,
actionButtonTemplate,
inputFilterTemplate,
@@ -148,8 +146,7 @@ define([
"depends": [
"stylesheets[]",
"$document",
"THEME",
"ASSETS_PATH"
"THEME"
]
},
{
@@ -161,7 +158,7 @@ define([
],
"filters": [
{
"implementation": ReverseFilter,
"implementation": "filters/ReverseFilter.js",
"key": "reverse"
}
],
@@ -207,6 +204,7 @@ define([
"implementation": TimeRangeController,
"depends": [
"$scope",
"$timeout",
"formatService",
"DEFAULT_TIME_FORMAT",
"now"
@@ -407,11 +405,6 @@ define([
"key": "THEME",
"value": "unspecified",
"priority": "fallback"
},
{
"key": "ASSETS_PATH",
"value": ".",
"priority": "fallback"
}
],
"containers": [

View File

@@ -100,5 +100,6 @@
<glyph unicode="&#xe620;" glyph-name="icon-tabular-realtime" d="M896 960h-768c-70.606-0.215-127.785-57.394-128-127.979v-768.021c0.215-70.606 57.394-127.785 127.979-128h768.021c70.606 0.215 127.785 57.394 128 127.979v768.021c-0.215 70.606-57.394 127.785-127.979 128zM448 668l25.060-25.32c7.916-7.922 18.856-12.822 30.94-12.822s23.024 4.9 30.94 12.822l75.5 76.3c29.97 30.338 71.571 49.128 117.56 49.128s87.59-18.79 117.544-49.112l50.456-50.997v-152.2c-24.111 8.83-44.678 22.255-61.542 39.342l-75.518 76.318c-7.916 7.922-18.856 12.822-30.94 12.822s-23.024-4.9-30.94-12.822l-75.5-76.3c-29.971-30.343-71.575-49.137-117.568-49.137-20.084 0-39.331 3.584-57.137 10.146l1.145 151.831zM320 0h-192c-35.26 0.214-63.786 28.74-64 63.98v128.020h256v-192zM320 256h-256v192h256v-192zM320 512h-256v192h256v-192zM640 0h-256v192h256v-192zM448 323.38v174.5c1.88-1.74 3.74-3.5 5.56-5.34l75.5-76.3c7.916-7.922 18.856-12.822 30.94-12.822s23.024 4.9 30.94 12.822l75.5 76.3c29.966 30.333 71.56 49.119 117.542 49.119 43.28 0 82.673-16.644 112.128-43.879l-0.11-174.399c-1.88 1.74-3.74 3.5-5.56 5.34l-75.5 76.3c-7.916 7.922-18.856 12.822-30.94 12.822s-23.024-4.9-30.94-12.822l-75.5-76.3c-29.966-30.333-71.56-49.119-117.542-49.119-43.28 0-82.673 16.644-112.128 43.879zM960 64c-0.214-35.26-28.74-63.786-63.98-64h-192.020v192h256v-128z" />
<glyph unicode="&#xe621;" glyph-name="icon-tabular-lad" d="M896 960h-768c-70.606-0.215-127.785-57.394-128-127.979v-768.021c0.215-70.606 57.394-127.785 127.979-128h768.021c70.606 0.215 127.785 57.394 128 127.979v768.021c-0.215 70.606-57.394 127.785-127.979 128zM64 704h256v-192h-256v192zM64 448h256v-192h-256v192zM128 0c-35.26 0.214-63.786 28.74-64 63.98v128.020h256v-192h-192zM384 0v192h256v-192h-256zM960 64c-0.214-35.26-28.74-63.786-63.98-64h-192.020v192h256v-128zM960 448v-192h-576v192h64v64h-64v192h576v-192h-64v-64h64zM782.32 412.62l-110.32 55.16v172.22c0 17.673-14.327 32-32 32s-32-14.327-32-32v-211.78l145.68-72.84c4.172-2.133 9.1-3.383 14.32-3.383 17.675 0 32.003 14.328 32.003 32.003 0 12.454-7.114 23.247-17.501 28.536z" />
<glyph unicode="&#xe622;" glyph-name="icon-tabular-lad-set" d="M128 192v576c-70.606-0.215-127.785-57.394-128-127.979v-576.021c0.215-70.606 57.394-127.785 127.979-128h576.021c70.606 0.215 127.785 57.394 128 127.979l-576 0.021c-70.606 0.215-127.785 57.394-128 127.979zM896 960h-576c-70.606-0.215-127.785-57.394-128-127.979v-576.021c0.215-70.606 57.394-127.785 127.979-128h576.021c70.606 0.215 127.785 57.394 128 127.979v576.021c-0.215 70.606-57.394 127.785-127.979 128zM256 768h192v-128h-192v128zM256 576h192v-192h-192v192zM320 192c-35.26 0.214-63.786 28.74-64 63.98v64.020h192v-128h-128zM512 192v128h192v-128h-192zM960 256c-0.214-35.26-28.74-63.786-63.98-64h-128.020v128h192v-64zM960 384h-448v384h448v-384zM832 480c0.002 0 0.005 0 0.007 0 17.673 0 32 14.327 32 32 0 14.055-9.062 25.994-21.662 30.293l-74.345 24.767v104.94c0 17.673-14.327 32-32 32s-32-14.327-32-32v-151.060l117.88-39.3c3.018-1.040 6.495-1.64 10.113-1.64 0.003 0 0.005 0 0.008 0z" />
<glyph unicode="&#xe623;" glyph-name="icon-download" d="M832 384v-255.66l-0.34-0.34-639.66 0.34v255.66h-192v-256c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v256h-192zM512 320l448 448h-256v192h-384v-192h-256l448-448z" />
<glyph unicode="&#xe642;" glyph-name="icon-x" d="M384 448l-365.332-365.332c-24.89-24.89-24.89-65.62 0-90.51l37.49-37.49c24.89-24.89 65.62-24.89 90.51 0 0 0 365.332 365.332 365.332 365.332l365.332-365.332c24.89-24.89 65.62-24.89 90.51 0l37.49 37.49c24.89 24.89 24.89 65.62 0 90.51l-365.332 365.332c0 0 365.332 365.332 365.332 365.332 24.89 24.89 24.89 65.62 0 90.51l-37.49 37.49c-24.89 24.89-65.62 24.89-90.51 0 0 0-365.332-365.332-365.332-365.332l-365.332 365.332c-24.89 24.89-65.62 24.89-90.51 0l-37.49-37.49c-24.89-24.89-24.89-65.62 0-90.51 0 0 365.332-365.332 365.332-365.332z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,91 @@
@include keyframes(rotation) {
100% { @include transform(rotate(360deg)); }
}
@include keyframes(rotation-centered) {
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@include keyframes(clock-hands) {
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@include keyframes(clock-hands-sticky) {
0% {
@include transform(translate(-50%, -50%) rotate(0deg));
}
7% {
@include transform(translate(-50%, -50%) rotate(0deg));
}
8% {
@include transform(translate(-50%, -50%) rotate(30deg));
}
15% {
@include transform(translate(-50%, -50%) rotate(30deg));
}
16% {
@include transform(translate(-50%, -50%) rotate(60deg));
}
24% {
@include transform(translate(-50%, -50%) rotate(60deg));
}
25% {
@include transform(translate(-50%, -50%) rotate(90deg));
}
32% {
@include transform(translate(-50%, -50%) rotate(90deg));
}
33% {
@include transform(translate(-50%, -50%) rotate(120deg));
}
40% {
@include transform(translate(-50%, -50%) rotate(120deg));
}
41% {
@include transform(translate(-50%, -50%) rotate(150deg));
}
49% {
@include transform(translate(-50%, -50%) rotate(150deg));
}
50% {
@include transform(translate(-50%, -50%) rotate(180deg));
}
57% {
@include transform(translate(-50%, -50%) rotate(180deg));
}
58% {
@include transform(translate(-50%, -50%) rotate(210deg));
}
65% {
@include transform(translate(-50%, -50%) rotate(210deg));
}
66% {
@include transform(translate(-50%, -50%) rotate(240deg));
}
74% {
@include transform(translate(-50%, -50%) rotate(240deg));
}
75% {
@include transform(translate(-50%, -50%) rotate(270deg));
}
82% {
@include transform(translate(-50%, -50%) rotate(270deg));
}
83% {
@include transform(translate(-50%, -50%) rotate(300deg));
}
90% {
@include transform(translate(-50%, -50%) rotate(300deg));
}
91% {
@include transform(translate(-50%, -50%) rotate(330deg));
}
99% {
@include transform(translate(-50%, -50%) rotate(330deg));
}
100% {
@include transform(translate(-50%, -50%) rotate(360deg));
}
}

View File

@@ -108,6 +108,9 @@
&.grows {
@include flex(1 1 auto);
}
&.contents-align-right {
text-align: right;
}
}
.flex-container {
// Apply to wrapping elements, mct-includes, etc.
@@ -121,17 +124,18 @@
.l-flex-row {
@include flex-direction(row);
&.flex-elem { @include flex(1 1 auto); }
.flex-elem {
> .flex-elem {
height: inherit;
line-height: inherit;
min-width: 0;
&.holder:not(:last-child) { margin-right: $interiorMargin; }
}
.flex-container { @include flex-direction(row); }
}
.l-flex-col {
@include flex-direction(column);
.flex-elem {
> .flex-elem {
min-height: 0;
&.holder:not(:last-child) { margin-bottom: $interiorMarginLg; }
}

View File

@@ -48,8 +48,8 @@ $uePaneMiniTabW: 10px;
$uePaneMiniTabCollapsedW: 11px;
$ueEditLeftPaneW: 75%;
$treeSearchInputBarH: 25px;
$ueTimeControlH: (33px, 18px, 20px);
// Panes
$ueTimeControlH: (25px, 6px, 20px);
/*************** Panes */
$ueBrowseLeftPaneTreeMinW: 150px;
$ueBrowseLeftPaneTreeMaxW: 35%;
$ueBrowseLeftPaneTreeW: 25%;
@@ -57,48 +57,59 @@ $ueBrowseRightPaneInspectMinW: 200px;
$ueBrowseRightPaneInspectMaxW: 35%;
$ueBrowseRightPaneInspectW: 20%;
$ueDesktopMinW: 600px;
// Overlay
/*************** Overlay */
$ovrTopBarH: 45px;
$ovrFooterH: 24px;
$overlayMargin: 25px;
// Items
/*************** Items */
$ueBrowseGridItemLg: 200px;
$ueBrowseGridItemTopBarH: 20px;
$ueBrowseGridItemBottomBarH: 30px;
$itemPadLR: 5px;
// Tree
/*************** Tree */
$treeVCW: 10px;
$treeTypeIconH: 1.4em; // was 16px
$treeTypeIconHPx: 16px;
$treeTypeIconW: 18px;
$treeContextTriggerW: 20px;
// Tabular
/*************** Tabular */
$tabularHeaderH: 22px; //18px
$tabularTdPadLR: $itemPadLR;
$tabularTdPadTB: 3px;
// Imagery
/*************** Imagery */
$imageMainControlBarH: 25px;
$imageThumbsD: 120px;
$imageThumbsWrapperH: $imageThumbsD * 1.4;
$imageThumbPad: 1px;
// Ticks
/*************** Ticks */
$ticksH: 25px;
$tickLblVMargin: 3px;
$tickLblH: 15px;
$tickLblW: 50px;
$tickH: $ticksH - $tickLblVMargin - $tickLblH;
$tickW: 1px;
// Bubbles
/*************** Plots */
$plotYBarW: 60px;
$plotYLabelMinH: 20px;
$plotYLabelW: 10px;
$plotXBarH: 32px;
$plotLegendH: 20px;
$plotSwatchD: 8px;
// 1: Top, 2: right, 3: bottom, 4: left
$plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH + $interiorMargin, $plotYBarW);
/* min plot height is based on user testing to find minimum useful height */
$plotMinH: 95px;
/*************** Bubbles */
$bubbleArwSize: 10px;
$bubblePad: $interiorMargin;
$bubbleMinW: 100px;
$bubbleMaxW: 300px;
// Forms
/*************** Forms */
$reqSymbolW: 15px;
$reqSymbolM: $interiorMargin * 2;
$reqSymbolFontSize: 0.7em;
// Wait Spinner Defaults
$inputTextP: 3px 5px;
/*************** Wait Spinner Defaults */
$waitSpinnerD: 32px;
$waitSpinnerTreeD: 20px;
$waitSpinnerBorderW: 5px;
@@ -124,6 +135,8 @@ $dirImgs: $dirCommonRes + 'images/';
/************************** TIMINGS */
$controlFadeMs: 100ms;
$browseToEditAnimMs: 400ms;
$editBorderPulseMs: 500ms;
/************************** LIMITS */
$glyphLimit: '\e603';

View File

@@ -39,15 +39,20 @@
@include pulse($animName: pulse-subtle, $dur: 500ms, $opacity0: 0.7);
}
@mixin pulseBorder($c: red, $dur: 500ms, $iteration: infinite, $delay: 0s, $opacity0: 0, $opacity100: 1) {
@include keyframes(pulseBorder) {
0% { border-color: rgba($c, $opacity0); }
100% { border-color: rgba($c, $opacity100); }
@mixin animTo($animName, $propName, $propValStart, $propValEnd, $dur: 500ms, $delay: 0) {
@include keyframes($animName) {
from { #{propName}: $propValStart; }
to { #{$propName}: $propValEnd; }
}
@include animation-name(pulseBorder);
@include animation-duration($dur);
@include animation-direction(alternate);
@include animation-iteration-count($iteration);
@include animation-timing-function(ease);
@include animation-delay($delay);
@include animToParams($animName, $dur: 500ms, $delay: 0)
}
@mixin animToParams($animName, $dur: 500ms, $delay: 0) {
@include animation-name($animName);
@include animation-duration($dur);
@include animation-delay($delay);
@include animation-fill-mode(both);
@include animation-direction(normal);
@include animation-iteration-count(1);
@include animation-timing-function(ease-in-out);
}

View File

@@ -66,7 +66,7 @@ input, textarea {
input[type="text"],
input[type="search"] {
vertical-align: baseline;
padding: 3px 5px;
padding: $inputTextP;
}
h1, h2, h3 {

View File

@@ -66,8 +66,7 @@
}
.menu .type-icon,
.tree-item .type-icon,
.super-menu.menu .type-icon {
.tree-item .type-icon {
position: absolute;
}

View File

@@ -21,6 +21,7 @@
*****************************************************************************/
@import "effects";
@import "global";
@import "animations";
@import "archetypes";
@import "about";
@import "text";
@@ -40,7 +41,7 @@
@import "controls/lists";
@import "controls/menus";
@import "controls/messages";
@import "controls/time-controller";
@import "controls/time-conductor";
@import "mobile/controls/menus";
/********************************* FORMS */

View File

@@ -185,21 +185,15 @@
}
@mixin sliderTrack($bg: $scrollbarTrackColorBg) {
//$b: 1px solid lighten($bg, 30%);
border-radius: 2px;
box-sizing: border-box;
@include boxIncised(0.7);
background-color: $bg;
//border-bottom: $b;
//border-right: $b;
}
@mixin controlGrippy($b, $direction: horizontal, $w: 1px, $style: dotted) {
//&:before {
//@include trans-prop-nice("border-color", 25ms);
content: '';
display: block;
//height: auto;
pointer-events: none;
position: absolute;
z-index: 2;
@@ -305,7 +299,6 @@
border-radius: $controlCr;
box-sizing: border-box;
color: $fg;
display: inline-block;
}
@mixin btnBase($bg: $colorBodyBg, $bgHovColor: none, $fg: $colorBodyFg, $ic: $colorBtnIcon) {
@@ -348,7 +341,6 @@
display: inline-block;
font-family: 'symbolsfont';
margin-left: $interiorMarginSm;
vertical-align: top;
}
@mixin nice-textarea($bg: $colorBodyBg, $fg: $colorBodyFg) {

View File

@@ -33,18 +33,11 @@ $pad: $interiorMargin * $baseRatio;
.s-btn {
box-sizing: border-box;
display: inline-block;
padding: 0 $pad;
font-size: 0.7rem;
vertical-align: top;
.icon {
font-size: 0.8rem;
color: $colorKey;
}
.title-label {
vertical-align: top;
}
@include btnSubtle($colorBtnBg, $colorBtnBgHov, $colorBtnFg, $colorBtnIcon);
&.lg {
font-size: 1rem;
@@ -58,19 +51,14 @@ $pad: $interiorMargin * $baseRatio;
padding: 0 ($pad / $baseRatio) / 2;
}
&.major {
&.major,
&.key-edit,
&.key-properties {
$bg: $colorBtnMajorBg;
$hc: lighten($bg, 10%);
@include btnSubtle($bg, $hc, $colorBtnMajorFg, $colorBtnMajorFg);
}
&:not(.major) {
// bg, bgHov, fg, ic
@include btnSubtle($colorBtnBg, $colorBtnBgHov, $colorBtnFg, $colorBtnIcon);
}
&.pause-play {
}
&.t-save:before {
content:'\e612';
font-family: symbolsfont;
@@ -109,6 +97,22 @@ $pad: $interiorMargin * $baseRatio;
content: "\000039";
}
}
&.t-export {
&:before {
@extend .ui-symbol;
@extend .icon;
content: '\e623';
}
}
.icon {
font-size: 0.8rem;
}
.title-label {
vertical-align: top;
}
}
.s-icon-btn {
@@ -275,4 +279,3 @@ body.desktop .mini-tab-icon {
color: $colorPausedBg !important;
}
}

View File

@@ -420,6 +420,63 @@ input[type="search"] {
}
}
@mixin sliderKnob() {
$h: 16px;
cursor: pointer;
width: floor($h/1.75);
height: $h;
margin-top: 1 + floor($h/2) * -1;
@include btnSubtle(pullForward($colorBtnBg, 10%));
//border-radius: 50% !important;
}
@mixin sliderKnobRound() {
$h: 12px;
cursor: pointer;
width: $h;
height: $h;
margin-top: 1 + floor($h/2) * -1;
@include btnSubtle(pullForward($colorBtnBg, 10%));
border-radius: 50% !important;
}
input[type="range"] {
// HTML5 range inputs
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
background: transparent; /* Otherwise white in Chrome */
&:focus {
outline: none; /* Removes the blue border. */
}
// Thumb
&::-webkit-slider-thumb {
-webkit-appearance: none;
@include sliderKnobRound();
}
&::-moz-range-thumb {
border: none;
@include sliderKnobRound();
}
&::-ms-thumb {
border: none;
@include sliderKnobRound();
}
// Track
&::-webkit-slider-runnable-track {
width: 100%;
height: 3px;
@include sliderTrack();
}
&::-moz-range-track {
width: 100%;
height: 3px;
@include sliderTrack();
}
}
/******************************************************** DATETIME PICKER */
.l-datetime-picker {
$r1H: 15px;

View File

@@ -167,7 +167,7 @@
}
.pane {
box-sizing: border-box;
&.left {
&.menu-items {
border-right: 1px solid pullForward($colorMenuBg, 10%);
left: 0;
padding-right: $interiorMargin;
@@ -183,38 +183,53 @@
}
}
}
&.right {
&.menu-item-description {
left: auto;
right: 0;
padding: $interiorMargin * 5;
width: $prw;
.desc-area {
&.icon {
color: $colorCreateMenuLgIcon;
font-size: 8em;
margin-bottom: $interiorMargin * 3;
position: relative;
text-align: center;
}
&.title {
color: $colorCreateMenuText;
font-size: 1.2em;
margin-bottom: $interiorMargin * 2;
}
&.description {
color: pushBack($colorCreateMenuText, 20%);
font-size: 0.8em;
line-height: 1.5em;
}
}
}
}
.menu-item-description {
.desc-area {
&.icon {
$h: 150px;
color: $colorCreateMenuLgIcon;
position: relative;
font-size: 8em;
left: 0;
height: $h;
line-height: $h;
margin-bottom: $interiorMargin * 5;
text-align: center;
}
&.title {
color: $colorCreateMenuText;
font-size: 1.2em;
margin-bottom: 0.5em;
}
&.description {
color: $colorCreateMenuText;
font-size: 0.8em;
line-height: 1.5em;
}
}
}
&.mini {
width: 400px;
height: 300px;
.pane {
&.menu-items {
font-size: 0.8em;
}
&.menu-item-description {
padding: $interiorMargin * 3;
.desc-area {
&.icon {
font-size: 4em;
}
&.title {
font-size: 1em;
}
}
}
}
}
}
.context-menu {
font-size: 0.80rem;
@@ -251,3 +266,7 @@
right: 0;
width: auto;
}
.menus-up .menu {
bottom: $btnStdH; top: auto;
}

View File

@@ -0,0 +1,376 @@
@mixin toiLineHovEffects() {
&:before,
&:after {
background-color: $timeControllerToiLineColorHov;
}
}
.l-time-conductor-holder {
$minW: 500px;
border-top: 1px solid $colorInteriorBorder;
min-width: $minW;
padding-top: $interiorMargin;
}
.time-conductor-icon {
$c: $colorObjHdrIc;
$d: 20px;
background: $c;
border-radius: 4px;
height: $d !important;
width: $d;
position: relative;
// Icon shape: brackets
&:before,
&:after {
content: '';
background: $colorBodyBg;
position: absolute;
}
&:before {
$in: 7px;
left: $in;
top: 0;
right: $in;
bottom: 0;
}
&:after {
$in: 4px;
left: $in;
top: $in;
right: $in;
bottom: $in;
}
// Clock hands
div[class*="hand"] {
$handW: 2px;
$handH: 8px;
@include transform(translate(-50%, -50%));
@include animation-iteration-count(infinite);
@include animation-timing-function(linear);
position: absolute;
height: $handW;
width: $handW;
left: 50%;
top: 50%;
z-index: 2;
&:before {
background-color: $c;
content: '';
display: block;
position: absolute;
width: 100%;
bottom: -1px;
}
&.hand-little {
z-index: 2;
@include animation-duration(12s);
&:before {
//background: red;
height: ceil($handH * 0.7);
}
}
&.hand-big {
z-index: 1;
@include animation-duration(1s);
&:before {
height: $handH;
}
}
}
}
.l-time-conductor {
$knobHOffset: 0px;
$rangeValPad: $interiorMargin;
$rangeValOffset: $sliderKnobW + $interiorMargin;
$r1H: nth($ueTimeControlH, 1);
$r2H: nth($ueTimeControlH, 2);
$r3H: nth($ueTimeControlH, 3);
// Glyphs Todo: replace with refactored CSS approach when that is merged into master
$glyphIconFixed: '\e604';
$glyphIconRealtime: '\43';
$glyphIconLatest: '\44';
position: relative;
> .l-row-elem {
// First order row elements
box-sizing: border-box;
width: 100%;
position: relative;
}
.mode-selector .s-menu-btn,
.time-delta {
&:before {
@extend .ui-symbol;
}
}
.time-delta {
&:before {
color: $colorTimeCondKeyBg;
}
}
.l-time-conductor-inputs-holder,
.l-time-conductor-ticks,
.l-time-conductor-zoom-w {
font-size: 0.8rem;
}
.l-time-conductor-inputs-holder {
$trInputW: 180px;
$hmInputW: 60px;
$ticksBlockerFadeW: 50px;
$iconCalendarW: 16px;
$wBgColor: $colorBodyBg;
height: $r1H;
z-index: 1;
.l-time-range-w {
// Wraps a datetime text input field
height: 100%;
position: absolute;
&.start-w {
@include background-image(linear-gradient(270deg, transparent, $wBgColor $ticksBlockerFadeW));
padding-right: $ticksBlockerFadeW;
}
&.end-w {
@include background-image(linear-gradient(90deg, transparent, $wBgColor $ticksBlockerFadeW));
padding-left: $ticksBlockerFadeW;
right: 0;
text-align: right;
}
input[type="text"] {
@include trans-prop-nice(padding, 250ms);
}
.time-range-input input {
width: $trInputW;
}
.hrs-min-input input {
width: $hmInputW;
}
.icon-calendar {
margin-top: 4px;
}
}
}
.l-time-conductor-ticks {
$c: $colorTick;
height: $r1H;
mct-conductor-axis {
display: block;
position: relative;
width: 100%;
}
.l-axis-holder {
height: $r1H;
position: relative;
width: 100%;
svg {
text-rendering: geometricPrecision;
width: 100%;
height: 100%;
> g {
font-size: 0.9em;
}
path {
// Line beneath ticks
display: none;
}
line {
// Tick marks
stroke: $c;
}
text {
// Tick labels
fill: $c;
}
}
}
}
.l-data-visualization {
background: $colorTimeCondDataVisBg;
height: $r2H;
}
.l-time-conductor-controls {
align-items: center;
margin-top: $interiorMargin;
.l-time-conductor-zoom-w {
@include justify-content(flex-end);
.time-conductor-zoom {
height: $r3H;
min-width: 100px;
width: 20%;
}
.time-conductor-zoom-current-range {
color: $colorTick;
}
}
}
// Fixed
&.fixed-mode {
.time-conductor-icon div[class*="hand"] {
&.hand-little {
@include transform(rotate(120deg));
}
}
}
// Realtime, latest modes
&.realtime-mode,
&.latest-mode {
.time-conductor-icon {
background: $colorTimeCondKeyBg;
div[class*="hand"] {
@include animation-name(clock-hands);
&:before {
background: $colorTimeCondKeyBg;
}
}
}
.l-time-conductor-inputs-holder {
.l-time-range-input-w {
input[type="text"]:not(.error) {
background: transparent;
box-shadow: none;
border-radius: 0;
padding-left: 0;
padding-right: 0;
&:hover,
&:focus {
@include nice-input();
padding: $inputTextP;
}
}
&.start-date {
pointer-events: none;
}
.icon-calendar {
display: none;
}
&.end-date {
display: none;
}
}
}
&.realtime-mode .time-conductor-icon div[class*="hand"] { @include animation-name(clock-hands); }
&.latest-mode .time-conductor-icon div[class*="hand"] {
@include animation-name(clock-hands-sticky);
&.hand-big { @include animation-duration(5s); }
&.hand-little { @include animation-duration(60s); }
}
.l-data-visualization {
background: $colorTimeCondDataVisRtBg !important
}
.mode-selector .s-menu-btn {
@include btnSubtle($colorTimeCondKeyBg, pullForward($colorTimeCondKeyBg, $ltGamma), $colorTimeCondKeyFg);
}
}
&.fixed-mode {
$i: $glyphIconFixed;
.mode-selector .s-menu-btn:before {
content: $i;
}
}
&.realtime-mode {
$i: $glyphIconRealtime;
.time-delta:before {
content: $i;
}
.mode-selector .s-menu-btn:before {
content: $i;
}
}
&.latest-mode {
$i: $glyphIconLatest;
.time-delta:before {
content: $i;
}
.mode-selector .s-menu-btn:before {
content: $i;
}
}
}
.s-time-range-val {
border-radius: $controlCr;
background-color: $colorInputBg;
padding: 1px 1px 0 $interiorMargin;
}
/******************************************************************** MOBILE */
@include phoneandtablet {
.l-time-conductor {
min-width: 0;
.l-time-range-slider-holder,
.l-time-conductor-ticks {
display: none;
}
}
}
@include phone {
.l-time-conductor {
.l-time-conductor-inputs-holder {
&.l-flex-row,
.l-flex-row {
@include align-items(flex-start);
}
.l-time-range-inputs-elem {
&.type-icon {
margin-top: 3px;
}
}
.l-time-conductor-inputs-holder {
@include flex-direction(column);
.l-time-range-input-w:not(:first-child) {
&:not(:first-child) {
margin-top: $interiorMargin;
}
margin-right: 0;
}
.l-time-range-inputs-elem {
&.lbl {
display: none;
}
}
}
}
}
}
@include phonePortrait {
.l-time-conductor {
.l-time-conductor-inputs-holder {
.l-time-conductor-inputs-holder {
@include flex(1 1 auto);
padding-top: 25px; // Make room for the ever lovin' Time Domain Selector
.flex-elem {
@include flex(1 1 auto);
width: 100%;
}
input[type="text"] {
width: 100%;
}
}
}
}
.l-time-domain-selector {
right: auto;
left: 20px;
}
}

View File

@@ -1,266 +0,0 @@
@mixin toiLineHovEffects() {
&:before,
&:after {
background-color: $timeControllerToiLineColorHov;
}
}
.l-time-controller {
$minW: 500px;
$knobHOffset: 0px;
$knobM: ($sliderKnobW + $knobHOffset) * -1;
$rangeValPad: $interiorMargin;
$rangeValOffset: $sliderKnobW + $interiorMargin;
$timeRangeSliderLROffset: 150px + ($sliderKnobW * 2);
$r1H: nth($ueTimeControlH,1); // Not currently used
$r2H: nth($ueTimeControlH,2);
$r3H: nth($ueTimeControlH,3);
min-width: $minW;
font-size: 0.8rem;
.l-time-range-inputs-holder,
.l-time-range-slider-holder,
.l-time-range-ticks-holder
{
box-sizing: border-box;
position: relative;
&:not(:first-child) {
margin-top: $interiorMargin;
}
}
.l-time-range-slider,
.l-time-range-ticks {
@include absPosDefault(0, visible);
left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset;
}
.l-time-range-inputs-holder {
border-top: 1px solid $colorInteriorBorder;
padding-top: $interiorMargin;
&.l-flex-row,
.l-flex-row {
@include align-items(center);
.flex-elem {
height: auto;
line-height: normal;
}
}
.type-icon {
font-size: 120%;
vertical-align: middle;
}
.l-time-range-input-w,
.l-time-range-inputs-elem {
margin-right: $interiorMargin;
.lbl {
color: $colorPlotLabelFg;
}
.ui-symbol.icon {
font-size: 11px;
}
}
.l-time-range-input-w {
// Wraps a datetime text input field
position: relative;
input[type="text"] {
width: 200px;
&.picker-icon {
padding-right: 20px;
}
}
.icon-calendar {
position: absolute;
right: 5px;
top: 5px;
}
}
}
.l-time-range-slider-holder {
height: $r2H;
.range-holder {
box-shadow: none;
background: none;
border: none;
.range {
.toi-line {
$myC: $timeControllerToiLineColor;
$myW: 8px;
@include transform(translateX(50%));
position: absolute;
top: 0; right: 0; bottom: 0px; left: auto;
width: $myW;
height: auto;
z-index: 2;
&:before {
// Vert line
background-color: $myC;
position: absolute;
content: "";
top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1;
width: 1px;
}
}
&:hover .toi-line {
@include toiLineHovEffects;
}
}
}
&:not(:active) {
.knob,
.range {
@include transition-property(left, right);
@include transition-duration(500ms);
@include transition-timing-function(ease-in-out);
}
}
}
.l-time-range-ticks-holder {
height: $r3H;
.l-time-range-ticks {
border-top: 1px solid $colorTick;
.tick {
background-color: $colorTick;
border:none;
height: 5px;
width: 1px;
margin-left: -1px;
position: absolute;
&:first-child {
margin-left: 0;
}
.l-time-range-tick-label {
@include webkitProp(transform, translateX(-50%));
color: $colorPlotLabelFg;
display: inline-block;
font-size: 0.7rem;
position: absolute;
top: 5px;
white-space: nowrap;
z-index: 2;
}
}
}
}
.knob {
z-index: 2;
&:before {
$mTB: 2px;
$grippyW: 3px;
$mLR: ($sliderKnobW - $grippyW)/2;
@include bgStripes($c: pullForward($sliderColorKnob, 20%), $a: 1, $bgsize: 4px, $angle: 0deg);
content: '';
display: block;
position: absolute;
top: $mTB; right: $mLR; bottom: $mTB; left: $mLR;
}
.range-value {
@include trans-prop-nice-fade(.25s);
font-size: 0.7rem;
position: absolute;
height: $r2H;
line-height: $r2H;
white-space: nowrap;
z-index: 1;
}
&:hover {
.range-value {
color: $sliderColorKnobHov;
}
}
&.knob-l {
margin-left: $knobM;
.range-value {
text-align: right;
right: $rangeValOffset;
}
}
&.knob-r {
margin-right: $knobM;
.range-value {
left: $rangeValOffset;
}
&:hover + .range-holder .range .toi-line {
@include toiLineHovEffects;
}
}
}
.l-time-domain-selector {
position: absolute;
right: 0px;
top: $interiorMargin;
}
}
.s-time-range-val {
border-radius: $controlCr;
background-color: $colorInputBg;
padding: 1px 1px 0 $interiorMargin;
}
/******************************************************************** MOBILE */
@include phoneandtablet {
.l-time-controller {
min-width: 0;
.l-time-range-slider-holder,
.l-time-range-ticks-holder {
display: none;
}
}
}
@include phone {
.l-time-controller {
.l-time-range-inputs-holder {
&.l-flex-row,
.l-flex-row {
@include align-items(flex-start);
}
.l-time-range-inputs-elem {
&.type-icon {
margin-top: 3px;
}
}
.t-inputs-w {
@include flex-direction(column);
.l-time-range-input-w:not(:first-child) {
&:not(:first-child) {
margin-top: $interiorMargin;
}
margin-right: 0;
}
.l-time-range-inputs-elem {
&.lbl { display: none; }
}
}
}
}
}
@include phonePortrait {
.l-time-controller {
.l-time-range-inputs-holder {
.t-inputs-w {
@include flex(1 1 auto);
padding-top: 25px; // Make room for the ever lovin' Time Domain Selector
.flex-elem {
@include flex(1 1 auto);
width: 100%;
}
input[type="text"] {
width: 100%;
}
}
}
}
.l-time-domain-selector {
right: auto;
left: 20px;
}
}

View File

@@ -1,13 +1,10 @@
.l-image-main-wrapper,
.l-image-main,
.l-image-main-controlbar,
.l-image-thumbs-wrapper {
@include absPosDefault(0, false);
}
/*************************************** MAIN LAYOUT */
.l-image-main-wrapper {
//@include test();
@if $enableImageryThumbs == true {
bottom: $interiorMargin*2 + $imageThumbsWrapperH;
}
@@ -15,16 +12,14 @@
min-width: 150px;
.l-image-main {
background-color: $colorPlotBg;
bottom: $imageMainControlBarH + $interiorMargin;
margin-bottom: $interiorMargin;
}
.l-image-main-controlbar {
top: auto;
height: $imageMainControlBarH;
&.l-flex-row { @include align-items(center); }
}
}
.l-image-thumbs-wrapper {
//@include test(red);
top: auto;
height: $imageThumbsWrapperH;
}
@@ -44,24 +39,17 @@
background-repeat: no-repeat;
}
.l-image-main {
//cursor: crosshair;
}
.l-image-main-controlbar {
//@include test();
font-size: 0.8em;
line-height: $imageMainControlBarH;
line-height: inherit;
.left, .right {
direction: rtl;
overflow: hidden;
}
.left {
//@include test(red);
text-align: left;
}
.right {
//@include test(green);
z-index: 2;
}
.l-date,
@@ -71,7 +59,6 @@
.l-mag {
direction: ltr;
display: inline-block;
//white-space: nowrap;
&:before {
content: "\000049";
}

View File

@@ -19,15 +19,6 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
@include keyframes(rotation) {
100% { @include transform(rotate(360deg)); }
}
@include keyframes(rotation-centered) {
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@mixin spinner($b: 5px, $c: $colorKey) {
@include transform-origin(center);
@include animation-name(rotation-centered);

View File

@@ -24,6 +24,10 @@
height: 100%;
}
.tabular-holder {
@include absPosDefault();
}
.tabular,
table {
box-sizing: border-box;
@@ -162,4 +166,41 @@ table {
min-width: 150px;
}
}
}
/********************************************************** SPECIFIC TABULAR VIEWS */
.tabular-holder {
&.t-exportable {
$btnExportH: 25px;
.l-view-section {
top: $btnExportH + $interiorMargin;
}
}
}
.child-frame {
.tabular-holder {
&.t-exportable {
$btnExportH: $btnFrameH;
.s-btn.t-export {
@include trans-prop-nice(opacity, $dur: 50ms);
opacity: 0;
}
.l-view-section {
@include trans-prop-nice(top, $dur: 150ms, $delay: 50ms);
top: 0;
}
&:hover {
.s-btn.t-export {
@include trans-prop-nice(opacity, 150ms, 100ms);
opacity: 1;
}
.l-view-section {
@include trans-prop-nice(top, $dur: 150ms);
top: $btnExportH + $interiorMargin;
}
}
}
}
}

View File

@@ -19,12 +19,10 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
$yBarW: 60px;
$yLabelW: 10px;
$xBarH: 32px;
$legendH: 20px;
$swatchD: 8px;
$plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBarW); // Top, right, bottom, left
.abs.holder-plot {
// Fend off the scrollbar when less than min-height;
right: $interiorMargin;
}
.gl-plot {
color: $colorPlotFg;
@@ -32,6 +30,7 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
position: relative;
width: 100%;
height: 100%;
min-height: $plotMinH;
.gl-plot-local-controls {
@include trans-prop-nice(opacity, 150ms);
@@ -54,17 +53,17 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
top: auto;
right: 0;
bottom: $interiorMargin;
left: $yBarW;
height: $xBarH;
left: $plotYBarW;
height: $plotXBarH;
width: auto;
overflow: hidden;
}
&.gl-plot-y {
top: $legendH + $interiorMargin;
top: $plotLegendH + $interiorMargin;
right: auto;
bottom: nth($plotDisplayArea, 3);
left: 0;
width: $yBarW;
width: $plotYBarW;
}
}
@@ -146,7 +145,7 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
@include transform(translateY(-50%));
min-width: 150px; // Need this due to enclosure of .select
top: 50%;
left: $yLabelW + $interiorMargin * 2;
left: $plotYLabelW + $interiorMargin * 2;
}
.t-plot-display-controls {
@@ -174,7 +173,7 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
right: 0;
bottom: auto;
left: 0;
height: $legendH;
height: $plotLegendH;
overflow-x: hidden;
overflow-y: auto;
}
@@ -236,8 +235,8 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
.color-swatch {
border-radius: 2px;
display: inline-block;
height: $swatchD;
width: $swatchD;
height: $plotSwatchD;
width: $plotSwatchD;
}
}
}
@@ -249,8 +248,8 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
padding: 0px $itemPadLR;
.plot-color-swatch {
border: 1px solid $colorBodyBg;
height: $swatchD + 1;
width: $swatchD + 1;
height: $plotSwatchD + 1;
width: $plotSwatchD + 1;
}
}
}

View File

@@ -54,7 +54,8 @@
height: $ohH;
line-height: $ohH;
padding: 0 $interiorMargin;
> span {
> span,
&:before {
font-size: 0.65rem;
}
}

View File

@@ -129,6 +129,8 @@
}
.primary-pane {
// Clip element that have min-widths
overflow: hidden;
// Need to lift up this pane to ensure that 'collapsed' panes don't block user interactions
z-index: 4;
}
@@ -237,30 +239,10 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
top: $ueTopBarH + $interiorMarginLg;
}
.l-object-wrapper {
@extend .abs;
.object-holder-main {
@extend .abs;
}
.l-edit-controls {
//@include trans-prop-nice((opacity, height), 0.25s);
border-bottom: 1px solid $colorInteriorBorder;
line-height: $ueEditToolBarH;
height: 0px;
opacity: 0;
.tool-bar {
right: $interiorMargin;
}
}
}
.l-object-wrapper-inner {
@include trans-prop-nice-resize(0.25s);
}
.object-browse-bar .s-btn,
.top-bar .buttons-main .s-btn,
.top-bar .s-menu-btn,
@@ -288,8 +270,9 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
.left {
padding-right: $interiorMarginLg;
.l-back:not(.s-status-editing) {
.l-back {
margin-right: $interiorMarginLg;
&.s-status-editing { display: none; }
}
}
}
@@ -376,19 +359,49 @@ body.desktop {
.s-status-editing {
.l-object-wrapper {
@include pulseBorder($colorEditAreaFg, $dur: 1s, $opacity0: 0.3);
border-radius: $controlCr;
$t2Dur: $browseToEditAnimMs;
$t1Dur: $t2Dur / 2;
$pulseDur: $editBorderPulseMs;
$bC0: rgba($colorEditAreaFg, 0.5);
$bC100: rgba($colorEditAreaFg, 1);
background-color: $colorEditAreaBg;
border-color: $colorEditAreaFg;
border-width: 2px;
border-style: dotted;
.l-object-wrapper-inner {
@include absPosDefault(3px, hidden);
border-radius: $controlCr;
border: 1px dotted $bC0;
// Transition 1
@include keyframes(wrapperIn) {
from { border: 0px dotted transparent; padding: 0; }
to { border: 1px dotted $bC0; padding: 5px; }
}
// Do last
@include keyframes(pulseNew) {
from { border-color: $bC0; }
to { border-color: $bC100; }
}
@include animation-name(wrapperIn, pulseNew);
@include animation-duration($t1Dur, $pulseDur);
@include animation-delay(0s, $t1Dur + $t2Dur);
@include animation-direction(normal, alternate);
@include animation-fill-mode(both, none);
@include animation-iteration-count(1, infinite);
@include animation-timing-function(ease-in-out, linear);
.l-edit-controls {
height: $ueEditToolBarH + $interiorMargin;
margin-bottom: $interiorMargin;
opacity: 1;
height: 0;
border-bottom: 1px solid $colorInteriorBorder;
// Transition 2: reveal edit controls
@include keyframes(editIn) {
from { border-bottom: 0px solid transparent; height: 0; margin-bottom: 0; }
to { border-bottom: 1px solid $colorInteriorBorder; height: $ueEditToolBarH + $interiorMargin; margin-bottom: $interiorMargin; }
}
@include animToParams(editIn, $dur: $t2Dur, $delay: $t1Dur);
.tool-bar {
right: $interiorMargin;
}
}
}
}

View File

@@ -23,16 +23,19 @@
<input type="text"
ng-model="textValue"
ng-blur="restoreTextValue(); ngBlur()"
ng-mouseup="ngMouseup()"
ng-class="{
error: textInvalid ||
(structure.validate &&
!structure.validate(ngModel[field])),
'picker-icon': structure.format === 'utc' || !structure.format
}">
</input><a class="ui-symbol icon icon-calendar"
ng-if="structure.format === 'utc' || !structure.format"
ng-click="picker.active = !picker.active">
</a>
</input>
<a class="ui-symbol icon icon-calendar"
ng-if="!picker.active && (structure.format === 'utc' || !structure.format)"
ng-click="picker.active = !picker.active"></a>
<!-- If picker active show icon with no onclick to prevent double registration of clicks -->
<a class="ui-symbol icon icon-calendar" ng-if="picker.active"></a>
<mct-popup ng-if="picker.active">
<div mct-click-elsewhere="picker.active = false">
<mct-control key="'datetime-picker'"

View File

@@ -20,10 +20,10 @@
at runtime from the About dialog for additional information.
-->
<div ng-controller="TimeRangeController as trCtrl" class="l-flex-col">
<form class="l-time-range-inputs-holder l-flex-row flex-elem"
<form class="l-time-conductor-inputs-holder l-flex-row flex-elem"
ng-submit="trCtrl.updateBoundsFromForm()">
<span class="l-time-range-inputs-elem ui-symbol type-icon flex-elem">&#x43;</span>
<span class="l-time-range-inputs-elem t-inputs-w l-flex-row flex-elem">
<span class="l-time-range-inputs-elem l-flex-row flex-elem">
<span class="l-time-range-input-w flex-elem">
<mct-control key="'datetime-field'"
structure="{
@@ -86,7 +86,7 @@
</div>
</div>
<div class="l-time-range-ticks-holder flex-elem">
<div class="l-time-conductor-ticks flex-elem">
<div class="l-time-range-ticks">
<div
ng-repeat="tick in ticks track by $index"

View File

@@ -38,7 +38,7 @@ define(
* @param $document Angular's jqLite-wrapped document element
* @param {string} activeTheme the theme in use
*/
function StyleSheetLoader(stylesheets, $document, activeTheme, assetPath) {
function StyleSheetLoader(stylesheets, $document, activeTheme) {
var head = $document.find('head'),
document = $document[0];
@@ -47,7 +47,6 @@ define(
// Create a link element, and construct full path
var link = document.createElement('link'),
path = [
assetPath,
stylesheet.bundle.path,
stylesheet.bundle.resources,
stylesheet.stylesheetUrl

View File

@@ -72,6 +72,17 @@ define(
if ($scope.ngBlur) {
$scope.ngBlur();
}
// If picker is active, dismiss it when valid value has been selected
// This 'if' is to avoid unnecessary validation if picker is not active
if ($scope.picker.active) {
if ($scope.structure.validate && $scope.structure.validate($scope.ngModel[$scope.field])) {
$scope.picker.active = false;
} else if (!$scope.structure.validate) {
//If picker visible, but no validation function, hide picker
$scope.picker.active = false;
}
}
}
}
@@ -93,7 +104,6 @@ define(
$scope.$watch('ngModel[field]', updateFromModel);
$scope.$watch('pickerModel.value', updateFromPicker);
$scope.$watch('textValue', updateFromView);
}
return DateTimeFieldController;

View File

@@ -53,7 +53,7 @@ define([
* format has been otherwise specified
* @param {Function} now a function to return current system time
*/
function TimeRangeController($scope, formatService, defaultFormat, now) {
function TimeRangeController($scope, $timeout, formatService, defaultFormat, now) {
this.$scope = $scope;
this.formatService = formatService;
this.defaultFormat = defaultFormat;
@@ -66,6 +66,7 @@ define([
this.formatter = formatService.getFormat(defaultFormat);
this.formStartChanged = false;
this.formEndChanged = false;
this.$timeout = $timeout;
this.$scope.ticks = [];
@@ -259,18 +260,23 @@ define([
};
TimeRangeController.prototype.updateBoundsFromForm = function () {
if (this.formStartChanged) {
this.$scope.ngModel.outer.start =
this.$scope.ngModel.inner.start =
this.$scope.formModel.start;
this.formStartChanged = false;
}
if (this.formEndChanged) {
this.$scope.ngModel.outer.end =
this.$scope.ngModel.inner.end =
this.$scope.formModel.end;
this.formEndChanged = false;
}
var self = this;
//Allow Angular to trigger watches and determine whether values have changed.
this.$timeout(function () {
if (self.formStartChanged) {
self.$scope.ngModel.outer.start =
self.$scope.ngModel.inner.start =
self.$scope.formModel.start;
self.formStartChanged = false;
}
if (self.formEndChanged) {
self.$scope.ngModel.outer.end =
self.$scope.ngModel.inner.end =
self.$scope.formModel.end;
self.formEndChanged = false;
}
});
};
TimeRangeController.prototype.onFormStartChange = function (

View File

@@ -51,7 +51,9 @@ define(
yMax = yMin + rect.height;
if (x < xMin || x > xMax || y < yMin || y > yMax) {
scope.$eval(attrs.mctClickElsewhere);
scope.$apply(function () {
scope.$eval(attrs.mctClickElsewhere);
});
}
}

View File

@@ -33,6 +33,7 @@ define(
var mockScope,
mockFormatService,
testDefaultFormat,
mockTimeout,
mockNow,
mockFormat,
controller;
@@ -54,6 +55,10 @@ define(
}
beforeEach(function () {
mockTimeout = function (fn) {
return fn();
};
mockScope = jasmine.createSpyObj(
"$scope",
["$apply", "$watch", "$watchCollection"]
@@ -78,6 +83,7 @@ define(
controller = new TimeRangeController(
mockScope,
mockTimeout,
mockFormatService,
testDefaultFormat,
mockNow

View File

@@ -104,6 +104,8 @@ define(
});
it("triggers an evaluation of its related Angular expression", function () {
expect(mockScope.$apply).toHaveBeenCalled();
mockScope.$apply.mostRecentCall.args[0]();
expect(mockScope.$eval)
.toHaveBeenCalledWith(testAttrs.mctClickElsewhere);
});

View File

@@ -14,7 +14,7 @@ $colorAHov: #fff;
$contrastRatioPercent: 7%;
$hoverRatioPercent: 10%;
$basicCr: 3px;
$controlCr: 3px;
$controlCr: 2px;
$smallCr: 2px;
// Buttons and Controls
@@ -52,7 +52,7 @@ $colorGridLines: rgba(#fff, 0.05);
$colorInvokeMenu: #fff;
$colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: pullForward($colorObjHdrTxt, 20%);
$colorTick: rgba(white, 0.2);
$colorTick: pullForward($colorBodyBg, 20%);
// Menu colors
$colorMenuBg: pullForward($colorBodyBg, 23%);
@@ -183,12 +183,13 @@ $scrollbarThumbColorOverlay: lighten($colorOvrBg, 10%);
$scrollbarThumbColorOverlayHov: lighten($scrollbarThumbColorOverlay, 2%);
// Splitter
$splitterD: 25px; // splitterD and HandleD should both be odd, or even
$splitterD: 17px; // splitterD and $splitterHandleD should both be odd, or even
$splitterHandleD: 1px;
$splitterDSm: 17px; // Smaller splitter, used inside elements like a Timeline view
$colorSplitterBg: rgba(#fff, 0.1); //pullForward($colorBodyBg, 5%);
$splitterShdw: rgba(black, 0.4) 0 0 3px;
$splitterEndCr: none;
$colorSplitterHover: pullForward($colorBodyBg, 15%);
$colorSplitterHover: pullForward($colorBodyBg, 40%);
$colorSplitterActive: $colorKey;
// Mobile
@@ -206,4 +207,10 @@ $colorAboutLink: #84b3ff;
// Loading
$colorLoadingFg: $colorAlt1;
$colorLoadingBg: rgba($colorBodyFg, 0.2);
$colorLoadingBg: rgba($colorBodyFg, 0.2);
// Time Conductor
$colorTimeCondKeyBg: #4e70dc;
$colorTimeCondKeyFg: #fff;
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 10%);
$colorTimeCondDataVisRtBg: pushBack($colorTimeCondKeyBg, 10%);

View File

@@ -52,7 +52,7 @@ $colorGridLines: rgba(#000, 0.05);
$colorInvokeMenu: #fff;
$colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: pushBack($colorObjHdrTxt, 30%);
$colorTick: rgba(black, 0.2);
$colorTick: pullForward($colorBodyBg, 30%);
// Menu colors
$colorMenuBg: pushBack($colorBodyBg, 10%);
@@ -183,12 +183,13 @@ $scrollbarThumbColorOverlay: darken($colorOvrBg, 50%);
$scrollbarThumbColorOverlayHov: $scrollbarThumbColorHov;
// Splitter
$splitterD: 24px;
$splitterD: 16px; // splitterD and $splitterHandleD should both be odd, or even
$splitterHandleD: 2px;
$splitterDSm: 16px; // Smaller splitter, used inside elements like a Timeline view
$colorSplitterBg: pullForward($colorBodyBg, 10%);
$splitterShdw: none;
$splitterEndCr: none;
$colorSplitterHover: none;
$colorSplitterHover: pullForward($colorBodyBg, 30%);
$colorSplitterActive: $colorKey;
// Mobile
@@ -207,3 +208,9 @@ $colorAboutLink: #84b3ff;
// Loading
$colorLoadingFg: $colorAlt1;
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Time Conductor
$colorTimeCondKeyBg: #6178dc;
$colorTimeCondKeyFg: #fff;
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 10%);
$colorTimeCondDataVisRtBg: pushBack($colorTimeCondKeyBg, 30%);

View File

@@ -60,11 +60,6 @@ define(
this.$q = $q;
}
function getKey(id) {
var parts = id.split(":");
return parts.length > 1 ? parts.slice(1).join(":") : id;
}
/**
* Checks if the value returned is falsey, and if so returns a
* rejected promise
@@ -131,7 +126,7 @@ define(
// ...and persist
return persistenceFn.apply(persistenceService, [
this.getSpace(),
getKey(domainObject.getId()),
this.getKey(),
domainObject.getModel()
]).then(function (result) {
return rejectIfFalsey(result, self.$q);
@@ -159,7 +154,7 @@ define(
return this.persistenceService.readObject(
this.getSpace(),
this.domainObject.getId()
this.getKey()
).then(updateModel);
};
@@ -178,6 +173,17 @@ define(
return this.identifierService.parse(id).getSpace();
};
/**
* Get the key for this domain object in the given space.
*
* @returns {string} the key of the object in it's space.
*/
PersistenceCapability.prototype.getKey = function () {
var id = this.domainObject.getId();
return this.identifierService.parse(id).getKey();
};
return PersistenceCapability;
}
);

View File

@@ -35,7 +35,8 @@ define(
mockNofificationService,
mockCacheService,
mockQ,
id = "object id",
key = "persistence key",
id = "object identifier",
model,
SPACE = "some space",
persistence,
@@ -101,6 +102,7 @@ define(
});
mockIdentifierService.parse.andReturn(mockIdentifier);
mockIdentifier.getSpace.andReturn(SPACE);
mockIdentifier.getKey.andReturn(key);
persistence = new PersistenceCapability(
mockCacheService,
mockPersistenceService,
@@ -124,7 +126,7 @@ define(
expect(mockPersistenceService.createObject).toHaveBeenCalledWith(
SPACE,
id,
key,
model
);
});
@@ -138,7 +140,7 @@ define(
expect(mockPersistenceService.updateObject).toHaveBeenCalledWith(
SPACE,
id,
key,
model
);
});

View File

@@ -82,16 +82,6 @@ define(
expect(result.a.getModel()).toEqual(model);
});
//TODO: Disabled for NEM Beta
xit("provides a new, fully constituted domain object for a" +
" provided model", function () {
var model = { someKey: "some value"},
result;
result = provider.newObject("a", model);
expect(result.getId()).toEqual("a");
expect(result.getModel()).toEqual(model);
});
});
}
);

View File

@@ -81,6 +81,7 @@ define(
if (phase.toLowerCase() === 'preparing' && !this.dialog) {
this.dialog = this.dialogService.showBlockingMessage({
title: "Preparing to copy objects",
hint: "Do not navigate away from this page or close this browser tab while this message is displayed.",
unknownProgress: true,
severity: "info"
});

View File

@@ -24,10 +24,7 @@ define(
[],
function () {
var DISALLOWED_ACTIONS = [
"move",
"copy"
];
var DISALLOWED_ACTIONS = ["move"];
/**
* This policy prevents performing move/copy/link actions across

View File

@@ -70,27 +70,25 @@ define(
policy = new CrossSpacePolicy();
});
['move', 'copy'].forEach(function (key) {
describe("for " + key + " actions", function () {
beforeEach(function () {
testActionMetadata.key = key;
});
describe("for move actions", function () {
beforeEach(function () {
testActionMetadata.key = 'move';
});
it("allows same-space changes", function () {
expect(policy.allow(mockAction, sameSpaceContext))
.toBe(true);
});
it("allows same-space changes", function () {
expect(policy.allow(mockAction, sameSpaceContext))
.toBe(true);
});
it("disallows cross-space changes", function () {
expect(policy.allow(mockAction, crossSpaceContext))
.toBe(false);
});
it("disallows cross-space changes", function () {
expect(policy.allow(mockAction, crossSpaceContext))
.toBe(false);
});
it("allows actions with no selectedObject", function () {
expect(policy.allow(mockAction, {
domainObject: makeObject('a')
})).toBe(true);
});
it("allows actions with no selectedObject", function () {
expect(policy.allow(mockAction, {
domainObject: makeObject('a')
})).toBe(true);
});
});

View File

@@ -42,19 +42,11 @@ define(
function addWorker(worker) {
var key = worker.key;
if (!workerUrls[key]) {
if (worker.scriptUrl) {
workerUrls[key] = [
worker.bundle.path,
worker.bundle.sources,
worker.scriptUrl
].join("/");
} else if (worker.scriptText) {
var blob = new Blob(
[worker.scriptText],
{type: 'application/javascript'}
);
workerUrls[key] = URL.createObjectURL(blob);
}
workerUrls[key] = [
worker.bundle.path,
worker.bundle.sources,
worker.scriptUrl
].join("/");
sharedWorkers[key] = worker.shared;
}
}

View File

@@ -0,0 +1,85 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"./src/TimeConductor",
"./src/TimeConductorController",
"./src/MCTConductorAxis",
"text!./res/templates/time-conductor.html",
"text!./res/templates/mode-selector/mode-selector.html",
"text!./res/templates/mode-selector/mode-menu.html",
'legacyRegistry'
], function (
TimeConductor,
TimeConductorController,
MCTConductorAxis,
timeConductorTemplate,
modeSelectorTemplate,
modeMenuTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/conductor-v2", {
"extensions": {
"services": [
{
"key": "timeConductor",
"implementation": TimeConductor
}
],
"controllers": [
{
"key": "TimeConductorController",
"implementation": TimeConductorController,
"depends": [
"$scope",
"$timeout",
"timeConductor"
]
}
],
"directives": [
{
"key": "mctConductorAxis",
"implementation": MCTConductorAxis,
"depends": [
"timeConductor"
]
}
],
"representations": [
{
"key": "time-conductor",
"template": timeConductorTemplate
},
{
"key": "mode-selector",
"template": modeSelectorTemplate
},
{
"key": "mode-menu",
"template": modeMenuTemplate
}
]
}
});
});

View File

@@ -0,0 +1,49 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="contents">
<div class="pane left menu-items">
<ul>
<li ng-repeat="(selected, option) in ngModel.options"
ng-click="ngModel.selected=selected">
<a
ng-mouseover="representation.activeMetadata = option"
ng-mouseleave="representation.activeMetadata = undefined">
<span class="ui-symbol icon type-icon">
{{option.glyph}}
</span>
{{option.name}}
</a>
</li>
</ul>
</div>
<div class="pane right menu-item-description">
<div class="desc-area ui-symbol icon type-icon">
{{representation.activeMetadata.glyph}}
</div>
<div class="desc-area title">
{{representation.activeMetadata.name}}
</div>
<div class="desc-area description">
{{representation.activeMetadata.description}}
</div>
</div>
</div>

View File

@@ -0,0 +1,34 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<span ng-controller="ClickAwayController as modeController">
<div class="s-menu-btn"
ng-click="modeController.toggle()">
<span class="title-label">{{ngModel.options[ngModel.selected].label}}</span>
</div>
<div class="menu super-menu mini mode-selector-menu"
ng-show="modeController.isActive()">
<mct-representation mct-object="domainObject"
key="'mode-menu'"
ng-model="ngModel">
</mct-representation>
</div>
</span>

View File

@@ -0,0 +1,115 @@
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
<div ng-controller="TimeConductorController as tcController"
class="holder grows flex-elem l-flex-row l-time-conductor {{modeModel.selected}}-mode">
<div class="flex-elem holder time-conductor-icon">
<div class="hand-little"></div>
<div class="hand-big"></div>
</div>
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
<!-- Holds inputs and ticks -->
<div class="l-time-conductor-ticks l-row-elem l-flex-row flex-elem no-margin">
<form class="abs l-time-conductor-inputs-holder"
ng-submit="tcController.updateBoundsFromForm(formModel)">
<span class="l-time-range-w start-w">
<span class="l-time-range-input-w start-date">
<mct-control key="'datetime-field'"
structure="{
format: 'utc',
validate: tcController.validateStart
}"
ng-model="formModel"
ng-mouseup="tcController.changing['start'] = true"
ng-blur="tcController.changing['start'] = false; tcController.updateBoundsFromForm(formModel)"
field="'start'"
class="time-range-input">
</mct-control>
</span>
<span class="l-time-range-input-w time-delta start-delta"
ng-class="{'hide':(modeModel.selected === 'fixed')}">
-
<mct-control key="'datetime-field'"
structure="{
format: 'duration',
validate: tcController.validateStartDelta
}"
ng-model="formModel"
ng-mouseup="tcController.changing['startDelta'] = true"
ng-blur="tcController.changing['startDelta'] = false; tcController.updateDeltasFromForm(formModel)"
field="'startDelta'"
class="hrs-min-input">
</mct-control>
</span>
</span>
<span class="l-time-range-w end-w">
<span class="l-time-range-input-w time-delta end-delta"
ng-class="{'hide':(modeModel.selected === 'fixed')}">
+
<mct-control key="'datetime-field'"
structure="{
format: 'duration',
validate: tcController.validateEndDelta
}"
ng-model="formModel"
ng-mouseup="tcController.changing['endDelta'] = true"
ng-blur="tcController.changing['endDelta'] = false; tcController.updateDeltasFromForm(formModel)"
field="'endDelta'"
class="hrs-min-input">
</mct-control>
</span>
<span class="l-time-range-input-w end-date"
ng-controller="ToggleController as t2">
<mct-control key="'datetime-field'"
structure="{
format: 'utc',
validate: tcController.validateEnd
}"
ng-model="formModel"
ng-mouseup="tcController.changing['end'] = true"
ng-blur="tcController.changing['end'] = false; tcController.updateBoundsFromForm(formModel)"
field="'end'"
class="time-range-input">
</mct-control>
</span>
</span>
<input type="submit" class="hidden">
</form>
<mct-conductor-axis></mct-conductor-axis>
</div>
<!-- Holds data availability, time of interest -->
<div class="l-data-visualization l-row-elem l-flex-row flex-elem"></div>
<!-- Holds time system and session selectors, and zoom control -->
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
<mct-representation
key="'mode-selector'"
mct-object="domainObject"
ng-model="modeModel"
class="holder flex-elem menus-up mode-selector">
</mct-representation>
<mct-control
key="'menu-button'"
class="holder flex-elem menus-up time-system"
ng-model="conductorModel.timeSystem"
structure="{
text: 'UTC',
options: [
{name: 'UTC', key:'utc', glyph:'\u0043'},
{name: 'SCET', key:'scet', glyph:'\u0043'},
{name: 'SCLK', key:'sclk', glyph:'\u0043'}
]}">
</mct-control>
<!-- Zoom control -->
<div class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">Mars Minutes</span>
<input class="time-conductor-zoom flex-elem" type="range" />
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,100 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[
"zepto",
"d3"
],
function ($, d3) {
/**
* The mct-control will dynamically include the control
* for a form element based on a symbolic key. Individual
* controls are defined under the extension category
* `controls`; this allows plug-ins to introduce new form
* control types while still making use of the form
* generator to ensure an overall consistent form style.
* @constructor
* @memberof platform/forms
*/
function MCTConductorAxis(conductor) {
function link(scope, element, attrs, ngModelController) {
var target = element[0].firstChild,
height = target.offsetHeight,
padding = 1;
var vis = d3.select(target)
.append('svg:svg')
.attr('width', '100%')
.attr('height', height);
var xScale = d3.scaleUtc();
var xAxis = d3.axisTop();
// draw x axis with labels and move to the bottom of the chart area
var axisElement = vis.append("g")
.attr("transform", "translate(0," + (height - padding) + ")");
function setScale(start, end) {
var width = target.offsetWidth;
xScale.domain([new Date(start), new Date(end)])
.range([padding, width - padding * 2]);
xAxis.scale(xScale);
axisElement.call(xAxis);
}
scope.resize = function () {
setScale(conductor.bounds().start, conductor.bounds().end);
};
conductor.on('bounds', function (bounds) {
setScale(bounds.start, bounds.end);
});
//Set initial scale.
setScale(conductor.bounds().start, conductor.bounds().end);
}
return {
// Only show at the element level
restrict: "E",
template: "<div class=\"l-axis-holder\" mct-resize=\"resize()\"></div>",
// ngOptions is terminal, so we need to be higher priority
priority: 1000,
// Link function
link: link,
// Pass through Angular's normal input field attributes
scope: {
// Used to choose which form control to use
start: "=",
end: "="
}
};
}
return MCTConductorAxis;
}
);

View File

@@ -32,8 +32,7 @@ define(['EventEmitter'], function (EventEmitter) {
* The TimeConductor extends the EventEmitter class. A number of events are
* fired when properties of the time conductor change, which are
* documented below.
* @interface
* @memberof module:openmct
* @constructor
*/
function TimeConductor() {
EventEmitter.call(this);
@@ -59,8 +58,6 @@ define(['EventEmitter'], function (EventEmitter) {
* bounds, for example by views validating user inputs.
* @param bounds The start and end time of the conductor.
* @returns {string | true} A validation error, or true if valid
* @memberof module:openmct.TimeConductor#
* @method validateBounds
*/
TimeConductor.prototype.validateBounds = function (bounds) {
if ((bounds.start === undefined) ||
@@ -86,21 +83,18 @@ define(['EventEmitter'], function (EventEmitter) {
* time conductor ticks, regularly updating the bounds from a timing
* source appropriate to the selected time system and mode of the time
* conductor.
* @fires module:openmct.TimeConductor~follow
* @fires TimeConductor#follow
* @param {boolean} followMode
* @returns {boolean}
* @memberof module:openmct.TimeConductor#
* @method follow
*/
TimeConductor.prototype.follow = function (followMode) {
if (arguments.length > 0) {
this.followMode = followMode;
/**
* The TimeConductor has toggled into or out of follow mode.
* @event follow
* @memberof module:openmct.TimeConductor~
* @event TimeConductor#follow The TimeConductor has toggled
* into or out of follow mode.
* @property {boolean} followMode true if follow mode is
* enabled, otherwise false.
* enabled, otherwise false.
*/
this.emit('follow', this.followMode);
}
@@ -111,28 +105,23 @@ define(['EventEmitter'], function (EventEmitter) {
* @typedef {Object} TimeConductorBounds
* @property {number} start The start time displayed by the time conductor in ms since epoch. Epoch determined by current time system
* @property {number} end The end time displayed by the time conductor in ms since epoch.
* @memberof module:openmct.TimeConductor~
*/
/**
* Get or set the start and end time of the time conductor. Basic validation
* of bounds is performed.
*
* @param {module:openmct.TimeConductorBounds~TimeConductorBounds} newBounds
* @param {TimeConductorBounds} newBounds
* @throws {Error} Validation error
* @fires module:openmct.TimeConductor~bounds
* @returns {module:openmct.TimeConductorBounds~TimeConductorBounds}
* @memberof module:openmct.TimeConductor#
* @method bounds
* @fires TimeConductor#bounds
* @returns {TimeConductorBounds}
*/
TimeConductor.prototype.bounds = function (newBounds) {
if (arguments.length > 0) {
throwOnError(this.validateBounds(newBounds));
this.boundsVal = newBounds;
/**
* The start time, end time, or both have been updated.
* @event bounds
* @memberof module:openmct.TimeConductor~
* @event TimeConductor#bounds The start time, end time, or
* both have been updated
* @property {TimeConductorBounds} bounds
*/
this.emit('bounds', this.boundsVal);
@@ -145,21 +134,17 @@ define(['EventEmitter'], function (EventEmitter) {
* units, epoch, and other aspects of time representation. When changing
* the time system in use, new valid bounds must also be provided.
* @param {TimeSystem} newTimeSystem
* @param {module:openmct.TimeConductor~TimeConductorBounds} bounds
* @fires module:openmct.TimeConductor~timeSystem
* @param {TimeConductorBounds} bounds
* @fires TimeConductor#timeSystem
* @returns {TimeSystem} The currently applied time system
* @memberof module:openmct.TimeConductor#
* @method timeSystem
*/
TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) {
if (arguments.length >= 2) {
this.system = newTimeSystem;
/**
* The time system used by the time
* @event TimeConductor#timeSystem The time system used by the time
* conductor has changed. A change in Time System will always be
* followed by a bounds event specifying new query bounds.
*
* @event module:openmct.TimeConductor~timeSystem
* followed by a bounds event specifying new query bounds
* @property {TimeSystem} The value of the currently applied
* Time System
* */
@@ -178,19 +163,15 @@ define(['EventEmitter'], function (EventEmitter) {
* Get or set the Time of Interest. The Time of Interest is the temporal
* focus of the current view. It can be manipulated by the user from the
* time conductor or from other views.
* @fires module:openmct.TimeConductor~timeOfInterest
* @fires TimeConductor#timeOfInterest
* @param newTOI
* @returns {number} the current time of interest
* @memberof module:openmct.TimeConductor#
* @method timeOfInterest
*/
TimeConductor.prototype.timeOfInterest = function (newTOI) {
if (arguments.length > 0) {
this.toi = newTOI;
/**
* The Time of Interest has moved.
* @event timeOfInterest
* @memberof module:openmct.TimeConductor~
* @event TimeConductor#timeOfInterest The Time of Interest has moved.
* @property {number} Current time of interest
*/
this.emit('timeOfInterest', this.toi);

View File

@@ -0,0 +1,205 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
var FIFTEEN_MINUTES = 15 * 60 * 1000;
function TimeConductorController($scope, $timeout, conductor) {
var self = this;
this.$scope = $scope;
this.$timeout = $timeout;
this.conductor = conductor;
this.startDelta = FIFTEEN_MINUTES;
this.endDelta = 0;
this.changing = {
'start': false,
'end': false
};
$scope.formModel = {
startDelta: this.startDelta,
endDelta: this.endDelta
};
conductor.on('bounds', function (bounds) {
if (!self.changing['start']) {
$scope.formModel.start = bounds.start;
}
if (!self.changing['end']) {
$scope.formModel.end = bounds.end;
}
});
conductor.on('follow', function (follow){
$scope.followMode = follow;
});
Object.keys(TimeConductorController.prototype).filter(function (key) {
return typeof TimeConductorController.prototype[key] === 'function';
}).forEach(function (key) {
self[key] = self[key].bind(self);
});
$scope.$watch('modeModel.selected', this.switchMode);
$scope.modeModel = {
selected: 'fixed',
options: {
'fixed': {
glyph: '\ue604',
label: 'Fixed',
name: 'Fixed Timespan Mode',
description: 'Query and explore data that falls between two fixed datetimes.'
},
'realtime': {
glyph: '\u0043',
label: 'Real-time',
name: 'Real-time Mode',
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
},
'latest': {
glyph: '\u0044',
label: 'LAD',
name: 'LAD Mode',
description: 'Latest Available Data mode monitors real-time streaming data as it comes in. The Time Conductor and displays will only advance when data becomes available.'
}
}
}
$scope.$on('$destroy', function() {
if (self.mode) {
self.mode();
}
});
self.initialize();
}
TimeConductorController.prototype.initialize = function () {
var now = Math.ceil(Date.now() / 1000) * 1000;
//Set the time conductor to some default
this.conductor.bounds({start: now - FIFTEEN_MINUTES, end: now});
this.$scope.modeModel.selected = 'fixed';
this.conductor.follow(false);
};
TimeConductorController.prototype.validateStart = function (start) {
var bounds = this.conductor.bounds();
return this.conductor.validateBounds({start: start, end: bounds.end}) === true;
};
TimeConductorController.prototype.validateEnd = function (end) {
var bounds = this.conductor.bounds();
return this.conductor.validateBounds({start: bounds.start, end: end}) === true;
};
TimeConductorController.prototype.updateBoundsFromForm = function (formModel) {
var newBounds = {start: formModel.start, end: formModel.end};
if (this.conductor.validateBounds(newBounds) === true) {
this.conductor.bounds(newBounds);
}
};
TimeConductorController.prototype.validateStartDelta = function (startDelta) {
return startDelta > 0;
};
TimeConductorController.prototype.validateEndDelta = function (endDelta) {
return endDelta >= 0;
};
TimeConductorController.prototype.validateDeltas = function (formModel) {
// Validate that start Delta is some non-zero value, and that end
// delta is zero or positive (ie. 'now' or some time in the future).
return this.validateStartDelta(formModel.startDelta) && this.validateEndDelta(formModel.endDelta);
};
TimeConductorController.prototype.updateDeltasFromForm = function (formModel) {
if (this.validateDeltas(formModel)) {
//Calculate the previous 'true' end value (without delta)
var oldEnd = this.conductor.bounds().end - this.endDelta || 0;
this.startDelta = formModel.startDelta;
this.endDelta = formModel.endDelta;
var newBounds = {
start: oldEnd - this.startDelta,
end: oldEnd + this.endDelta
};
this.conductor.bounds(newBounds);
}
};
TimeConductorController.prototype.switchMode = function (newMode) {
if (this.mode) {
this.mode();
}
this.mode = TimeConductorController.modes[newMode].call(this);
};
TimeConductorController.modes = {
'fixed': function () {
this.conductor.follow(false);
},
'realtime': function () {
var tickInterval = 1000;
var conductor = this.conductor;
var $timeout = this.$timeout;
var self = this;
conductor.follow(true);
setBoundsToNow(self.startDelta, self.endDelta);
var timeoutPromise = $timeout(tick, tickInterval);
function setBoundsToNow(startDelta, endDelta) {
var now = Math.ceil(Date.now() / 1000) * 1000;
conductor.bounds({start: now - startDelta, end: now + endDelta});
}
function tick() {
setBoundsToNow(self.startDelta, self.endDelta);
timeoutPromise = $timeout(tick, tickInterval)
}
return function destroy() {
$timeout.cancel(timeoutPromise);
}
},
'latest': function () {
//Don't know what to do here yet...
this.conductor.follow(true);
}
};
return TimeConductorController;
}
);

View File

@@ -42,7 +42,7 @@ define(
'parent'
];
xdescribe("ConductorRepresenter", function () {
describe("ConductorRepresenter", function () {
var mockThrottle,
mockConductorService,
mockCompile,

View File

@@ -1,23 +1,18 @@
<div class="t-imagery" ng-controller="ImageryController as imagery">
<div
class="l-image-main-wrapper"
<div class="l-image-main-wrapper l-flex-col"
ng-mouseenter="showLocalControls = true;"
ng-mouseleave="showLocalControls = false;"
>
ng-mouseleave="showLocalControls = false;">
<div
class="l-local-controls s-local-controls"
ng-show="false && showLocalControls"
>
<a
class="s-btn"
ng-show="false && showLocalControls">
<a class="s-btn"
ng-click="plot.stepBackPanZoom()"
ng-show="1"
title="Restore previous pan/zoom">
<span class="ui-symbol icon">&lt;</span>
</a>
<a
class="s-btn"
<a class="s-btn"
ng-click="plot.unzoom()"
ng-show="1"
title="Reset pan/zoom">
@@ -25,29 +20,23 @@
</a>
</div>
<div
class="l-image-main s-image-main"
<div class="l-image-main s-image-main flex-elem grows"
ng-class="{ paused: imagery.paused(), stale:false }"
mct-background-image="imagery.getImageUrl()"
>
mct-background-image="imagery.getImageUrl()">
</div>
<div class="l-image-main-controlbar l-flex-row">
<div class="l-image-main-controlbar flex-elem l-flex-row">
<div class="left flex-elem grows">
<a
class="s-btn show-thumbs sm hidden"
ng-click="showThumbsBubble = (showThumbsBubble)? false:true"
><span class="ui-symbol icon"></span></a>
<a class="s-btn show-thumbs sm hidden"
ng-click="showThumbsBubble = (showThumbsBubble)? false:true"><span class="ui-symbol icon"></span></a>
<span class="l-timezone">{{imagery.getZone()}}</span>
<span class="l-time">{{imagery.getTime()}}</span>
<span class="l-date">{{imagery.getDate()}}</span>
</div>
<div class="right flex-elem">
<a
class="s-btn pause-play"
<a class="s-btn pause-play"
ng-click="imagery.paused(!imagery.paused())"
ng-class="{ paused: imagery.paused() }"
><span class="ui-symbol icon"></span></a>
ng-class="{ paused: imagery.paused() }"><span class="ui-symbol icon"></span></a>
<a href="{{imagery.getImageUrl()}}"
ng-if="imagery.getImageUrl()"
target="_blank"
@@ -58,8 +47,7 @@
class="s-btn l-mag s-mag ui-symbol vsm"
ng-click="clipped = false"
ng-show="clipped === true"
title="Not all of image is visible; click to reset."
></a>
title="Not all of image is visible; click to reset."></a>
</div>
</div>
</div>

View File

@@ -27,9 +27,6 @@ define([
"./src/controllers/TableOptionsController",
'../../commonUI/regions/src/Region',
'../../commonUI/browse/src/InspectorRegion',
"text!./res/templates/table-options-edit.html",
"text!./res/templates/rt-table.html",
"text!./res/templates/historical-table.html",
"legacyRegistry"
], function (
MCTTable,
@@ -38,9 +35,6 @@ define([
TableOptionsController,
Region,
InspectorRegion,
tableOptionsEditTemplate,
rtTableTemplate,
historicalTableTemplate,
legacyRegistry
) {
/**
@@ -115,7 +109,7 @@ define([
{
"key": "HistoricalTableController",
"implementation": HistoricalTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
"depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout"]
},
{
"key": "RealtimeTableController",
@@ -134,7 +128,7 @@ define([
"name": "Historical Table",
"key": "table",
"glyph": "\ue604",
"template": historicalTableTemplate,
"templateUrl": "templates/historical-table.html",
"needs": [
"telemetry"
],
@@ -145,7 +139,7 @@ define([
"name": "Real-time Table",
"key": "rt-table",
"glyph": "\ue620",
"template": rtTableTemplate,
"templateUrl": "templates/rt-table.html",
"needs": [
"telemetry"
],
@@ -163,7 +157,7 @@ define([
"representations": [
{
"key": "table-options-edit",
"template": tableOptionsEditTemplate
"templateUrl": "templates/table-options-edit.html"
}
],
"stylesheets": [

View File

@@ -1,8 +1,9 @@
<div ng-controller="HistoricalTableController">
<div ng-controller="HistoricalTableController" ng-class="{'loading': loading}">
<mct-table
headers="headers"
rows="rows"
enableFilter="true"
enableSort="true">
enableSort="true"
class="tabular-holder t-exportable">
</mct-table>
</div>

View File

@@ -1,4 +1,9 @@
<div class="l-view-section scrolling" style="overflow: auto;">
<a class="t-btn l-btn s-btn t-export"
ng-click="exportAsCSV()"
title="Export This View's Data">
Export
</a>
<div class="l-view-section scrolling" style="overflow: auto;" mct-resize="resize()">
<table class="sizing-table">
<tbody>
<tr>

View File

@@ -4,6 +4,7 @@
rows="rows"
enableFilter="true"
enableSort="true"
auto-scroll="autoScroll">
class="tabular-holder t-exportable"
auto-scroll="true">
</mct-table>
</div>

View File

@@ -25,6 +25,7 @@ define(
'./TelemetryTableController'
],
function (TableController) {
var BATCH_SIZE = 1000;
/**
* Extends TelemetryTableController and adds real-time streaming
@@ -35,32 +36,82 @@ define(
* @param telemetryFormatter
* @constructor
*/
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter) {
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter, $timeout) {
var self = this;
this.$timeout = $timeout;
this.timeoutHandle = undefined;
this.batchSize = BATCH_SIZE;
$scope.$on("$destroy", function () {
if (self.timeoutHandle) {
self.$timeout.cancel(self.timeoutHandle);
}
});
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
}
HistoricalTableController.prototype = Object.create(TableController.prototype);
/**
* Populates historical data on scope when it becomes available from
* the telemetry API
* Set provided row data on scope, and cancel loading spinner
* @private
*/
HistoricalTableController.prototype.addHistoricalData = function () {
var rowData = [],
self = this;
this.handle.getTelemetryObjects().forEach(function (telemetryObject) {
var series = self.handle.getSeries(telemetryObject) || {},
pointCount = series.getPointCount ? series.getPointCount() : 0,
i = 0;
for (; i < pointCount; i++) {
rowData.push(self.table.getRowValues(telemetryObject,
self.handle.makeDatum(telemetryObject, series, i)));
}
});
HistoricalTableController.prototype.doneProcessing = function (rowData) {
this.$scope.rows = rowData;
this.$scope.loading = false;
};
/**
* Processes an array of objects, formatting the telemetry available
* for them and setting it on scope when done
* @private
*/
HistoricalTableController.prototype.processTelemetryObjects = function (objects, offset, start, rowData) {
var telemetryObject = objects[offset],
series,
i = start,
pointCount,
end;
//No more objects to process
if (!telemetryObject) {
return this.doneProcessing(rowData);
}
series = this.handle.getSeries(telemetryObject);
pointCount = series.getPointCount();
end = Math.min(start + this.batchSize, pointCount);
//Process rows in a batch with size not exceeding a maximum length
for (; i < end; i++) {
rowData.push(this.table.getRowValues(telemetryObject,
this.handle.makeDatum(telemetryObject, series, i)));
}
//Done processing all rows for this object.
if (end >= pointCount) {
offset++;
end = 0;
}
// Done processing either a batch or an object, yield process
// before continuing processing
this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, objects, offset, end, rowData));
};
/**
* Populates historical data on scope when it becomes available from
* the telemetry API
*/
HistoricalTableController.prototype.addHistoricalData = function () {
if (this.timeoutHandle) {
this.$timeout.cancel(this.timeoutHandle);
}
this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, this.handle.getTelemetryObjects(), 0, 0, []));
};
return HistoricalTableController;

View File

@@ -12,7 +12,7 @@ define(
* @param element
* @constructor
*/
function MCTTableController($scope, $timeout, element) {
function MCTTableController($scope, $timeout, element, exportService) {
var self = this;
this.$scope = $scope;
@@ -46,6 +46,16 @@ define(
setDefaults($scope);
$scope.exportAsCSV = function () {
var headers = $scope.displayHeaders;
exportService.exportCSV($scope.displayRows.map(function (row) {
return headers.reduce(function (r, header) {
r[header] = row[header].text;
return r;
}, {});
}), { headers: headers });
};
$scope.toggleSort = function (key) {
if (!$scope.enableSort) {
return;
@@ -76,6 +86,12 @@ define(
*/
$scope.$on('add:row', this.addRow.bind(this));
$scope.$on('remove:row', this.removeRow.bind(this));
/*
* Listen for resize events to trigger recalculation of table width
*/
$scope.resize = this.setElementSizes.bind(this);
}
/**

View File

@@ -38,30 +38,7 @@ define(
function RealtimeTableController($scope, telemetryHandler, telemetryFormatter) {
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
$scope.autoScroll = false;
this.maxRows = 100000;
/*
* Determine if auto-scroll should be enabled. Is enabled
* automatically when telemetry type is string
*/
function hasStringTelemetry(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [];
return ranges.some(function (range) {
return range.format === 'string';
});
}
$scope.$watch('domainObject', function (domainObject) {
//When a domain object becomes available, check whether the
// view should auto-scroll to the bottom.
if (domainObject && hasStringTelemetry(domainObject)) {
$scope.autoScroll = true;
}
});
}
RealtimeTableController.prototype = Object.create(TableController.prototype);
@@ -91,6 +68,7 @@ define(
self.$scope.rows.length - 1);
}
});
this.$scope.loading = false;
};
return RealtimeTableController;

View File

@@ -72,10 +72,10 @@ define(
* Maintain a configuration object on scope that stores column
* configuration. On change, synchronize with object model.
*/
$scope.$watchCollection('configuration.table.columns', function (columns) {
if (columns) {
$scope.$watchCollection('configuration.table.columns', function (newColumns, oldColumns) {
if (newColumns !== oldColumns) {
self.domainObject.useCapability('mutation', function (model) {
model.configuration.table.columns = columns;
model.configuration.table.columns = newColumns;
});
self.domainObject.getCapability('persistence').persist();
}

View File

@@ -83,16 +83,24 @@ define(
* @private
*/
TelemetryTableController.prototype.registerChangeListeners = function () {
var self = this;
this.unregisterChangeListeners();
// When composition changes, re-subscribe to the various
// telemetry subscriptions
this.changeListeners.push(this.$scope.$watchCollection(
'domainObject.getModel().composition', this.subscribe.bind(this)));
'domainObject.getModel().composition',
function (newVal, oldVal) {
if (newVal !== oldVal) {
self.subscribe();
}
})
);
//Change of bounds in time conductor
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds',
this.subscribe.bind(this)));
this.subscribe.bind(this))
);
};
/**
@@ -132,6 +140,7 @@ define(
if (this.handle) {
this.handle.unsubscribe();
}
this.$scope.loading = true;
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
this.$scope.domainObject,

View File

@@ -81,7 +81,13 @@ define(
return {
restrict: "E",
template: TableTemplate,
controller: ['$scope', '$timeout', '$element', MCTTableController],
controller: [
'$scope',
'$timeout',
'$element',
'exportService',
MCTTableController
],
scope: {
headers: "=",
rows: "=",

View File

@@ -30,23 +30,21 @@ define(
var TEST_DOMAIN_VALUE = "some formatted domain value";
describe("A domain column", function () {
var mockDataSet,
var mockDatum,
testMetadata,
mockFormatter,
column;
beforeEach(function () {
mockDataSet = jasmine.createSpyObj(
"data",
["getDomainValue"]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
["formatDomainValue", "formatRangeValue"]
);
testMetadata = {
key: "testKey",
name: "Test Name"
name: "Test Name",
format: "Test Format"
};
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
@@ -57,24 +55,24 @@ define(
expect(column.getTitle()).toEqual("Test Name");
});
xit("looks up data from a data set", function () {
column.getValue(undefined, mockDataSet, 42);
expect(mockDataSet.getDomainValue)
.toHaveBeenCalledWith(42, "testKey");
});
describe("when given a datum", function () {
beforeEach(function () {
mockDatum = {
testKey: "testKeyValue"
};
});
xit("formats domain values as time", function () {
mockDataSet.getDomainValue.andReturn(402513731000);
it("looks up data from the given datum", function () {
expect(column.getValue(undefined, mockDatum))
.toEqual({ text: TEST_DOMAIN_VALUE });
});
// Should have just given the value the formatter gave
expect(column.getValue(undefined, mockDataSet, 42).text)
.toEqual(TEST_DOMAIN_VALUE);
it("uses formatter to format domain values as requested", function () {
column.getValue(undefined, mockDatum);
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith("testKeyValue", "Test Format");
});
// Make sure that service interactions were as expected
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith(402513731000);
expect(mockFormatter.formatRangeValue)
.not.toHaveBeenCalled();
});
});

View File

@@ -34,6 +34,8 @@ define(
mockDomainObject,
mockTable,
mockConfiguration,
mockAngularTimeout,
mockTimeoutHandle,
watches,
controller;
@@ -63,6 +65,11 @@ define(
watches[expression] = callback;
});
mockTimeoutHandle = jasmine.createSpy("timeoutHandle");
mockAngularTimeout = jasmine.createSpy("$timeout");
mockAngularTimeout.andReturn(mockTimeoutHandle);
mockAngularTimeout.cancel = jasmine.createSpy("cancelTimeout");
mockConfiguration = {
'range1': true,
'range2': true,
@@ -107,7 +114,7 @@ define(
]);
mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter);
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter, mockAngularTimeout);
controller.table = mockTable;
controller.handle = mockTelemetryHandle;
});
@@ -163,6 +170,13 @@ define(
controller.addHistoricalData(mockDomainObject, mockSeries);
// Angular timeout is called a minumum of twice, regardless
// of batch size used.
expect(mockAngularTimeout).toHaveBeenCalled();
mockAngularTimeout.mostRecentCall.args[0]();
expect(mockAngularTimeout.calls.length).toEqual(2);
mockAngularTimeout.mostRecentCall.args[0]();
expect(controller.$scope.rows.length).toBe(5);
expect(controller.$scope.rows[0]).toBe(mockRow);
});
@@ -198,7 +212,7 @@ define(
' object composition changes', function () {
controller.registerChangeListeners();
expect(watches['domainObject.getModel().composition']).toBeDefined();
watches['domainObject.getModel().composition']();
watches['domainObject.getModel().composition']([], []);
expect(controller.subscribe).toHaveBeenCalled();
});
@@ -219,6 +233,78 @@ define(
});
});
describe('Yields thread', function () {
var mockSeries,
mockRow;
beforeEach(function () {
mockSeries = {
getPointCount: function () {
return 5;
},
getDomainValue: function () {
return 'Domain Value';
},
getRangeValue: function () {
return 'Range Value';
}
};
mockRow = {'domain': 'Domain Value', 'range': 'Range Value'};
mockTelemetryHandle.makeDatum.andCallFake(function () {
return mockRow;
});
mockTable.getRowValues.andReturn(mockRow);
mockTelemetryHandle.getTelemetryObjects.andReturn([mockDomainObject]);
mockTelemetryHandle.getSeries.andReturn(mockSeries);
});
it('when row count exceeds batch size', function () {
controller.batchSize = 3;
controller.addHistoricalData(mockDomainObject, mockSeries);
//Timeout is called a minimum of two times
expect(mockAngularTimeout).toHaveBeenCalled();
mockAngularTimeout.mostRecentCall.args[0]();
expect(mockAngularTimeout.calls.length).toEqual(2);
mockAngularTimeout.mostRecentCall.args[0]();
//Because it yields, timeout will have been called a
// third time for the batch.
expect(mockAngularTimeout.calls.length).toEqual(3);
mockAngularTimeout.mostRecentCall.args[0]();
expect(controller.$scope.rows.length).toBe(5);
expect(controller.$scope.rows[0]).toBe(mockRow);
});
it('cancelling any outstanding timeouts', function () {
controller.batchSize = 3;
controller.addHistoricalData(mockDomainObject, mockSeries);
expect(mockAngularTimeout).toHaveBeenCalled();
mockAngularTimeout.mostRecentCall.args[0]();
controller.addHistoricalData(mockDomainObject, mockSeries);
expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle);
});
it('cancels timeout on scope destruction', function () {
controller.batchSize = 3;
controller.addHistoricalData(mockDomainObject, mockSeries);
//Destroy is used by parent class as well, so multiple
// calls are made to scope.$on
var destroyCalls = mockScope.$on.calls.filter(function (call) {
return call.args[0] === '$destroy';
});
//Call destroy function
expect(destroyCalls.length).toEqual(2);
destroyCalls[0].args[1]();
expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle);
});
});
});
}
);

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