Squash-merge open1193 into master.

Closes https://github.com/nasa/openmct/pull/1287.

Squashed commit of the following:

commit af9ba3095859684cb2465f1d5222a14db231fdb7
Merge: c98286d 31308b1
Author: Pete Richards <peter.l.richards@nasa.gov>
Date:   Fri Nov 25 14:57:32 2016 -0800

    Merge branch 'open1193' into open1287-integration

    Resolve conflicts in glyph files as documented in pull request:
    https://github.com/nasa/openmct/pull/1287#issuecomment-263030180

    Closes https://github.com/nasa/openmct/pull/1287

commit 31308b1076
Author: Andrew Henry <andrew.k.henry@nasa.gov>
Date:   Tue Nov 22 17:08:10 2016 +0000

    [Time Conductor] Addressed code review comments. Fixes #1287

commit db6386e21c
Author: Henry <akhenry@gmail.com>
Date:   Wed Nov 9 11:52:39 2016 -0800

    Removed redundant ConductorService

commit a9ec8db8c6
Merge: fc36674 dfa4591
Author: Henry <akhenry@gmail.com>
Date:   Wed Nov 9 08:23:07 2016 -0800

    Merge branch 'master' into open1193

commit fc36674b5c
Author: Henry <akhenry@gmail.com>
Date:   Wed Nov 9 08:23:01 2016 -0800

    Updated wording of docs

commit d0906baccf
Author: Henry <akhenry@gmail.com>
Date:   Fri Nov 4 10:48:05 2016 -0700

    Fixed TOI not showing

commit 099c56c407
Author: Henry <akhenry@gmail.com>
Date:   Fri Oct 28 16:46:06 2016 -0700

    Fixed failing tests

commit 7cc008ed01
Author: Henry <akhenry@gmail.com>
Date:   Fri Oct 28 10:28:41 2016 -0700

    Added tests for tables, TOI controller

commit b0901e83cb
Author: Henry <akhenry@gmail.com>
Date:   Thu Oct 27 10:42:31 2016 -0700

    Added tests for ConductorAxisController and TimeConductor

commit dfed0a0783
Author: Henry <akhenry@gmail.com>
Date:   Wed Oct 26 18:07:50 2016 -0700

    Added ConductorAxisController tests

commit c3322e3847
Author: Henry <akhenry@gmail.com>
Date:   Tue Oct 25 20:54:24 2016 -0700

    Added tests for MctTableController

commit 8e76ebb5a8
Author: Henry <akhenry@gmail.com>
Date:   Tue Oct 25 15:48:15 2016 -0700

    Removed debugging code

commit 93735bc404
Author: Henry <akhenry@gmail.com>
Date:   Mon Oct 24 15:13:45 2016 -0700

    Removed bundle definition of MctAxisController
    Documenting code

commit a942541724
Author: Henry <akhenry@gmail.com>
Date:   Sun Oct 23 19:54:01 2016 -0700

    [Time Conductor] Fixed memory leak due to listeners not being deregistered

commit 49e600dcc9
Author: Henry <akhenry@gmail.com>
Date:   Sat Oct 22 16:47:11 2016 -0700

    [Time Conductor] Fixed zoom slider behavior

commit f5806613b9
Author: Henry <akhenry@gmail.com>
Date:   Sat Oct 22 14:46:14 2016 -0700

    [Time Conductor] support TOI from real-time tables

commit 029d2b3058
Author: Henry <akhenry@gmail.com>
Date:   Thu Oct 20 14:32:34 2016 -0700

    [Examples] Simplified MSL example, fixed object tree not loading by default, renamed. Fixes #1256. Fixes #1255

commit 482eb4a5e8
Author: Henry <akhenry@gmail.com>
Date:   Sat Oct 22 13:09:05 2016 -0700

    [Time Conductor] Using new API

commit 843c678b0b
Merge: b56ab0a 08ca765
Author: Henry <akhenry@gmail.com>
Date:   Fri Oct 21 10:21:22 2016 -0700

    In process of merging

commit b56ab0aac2
Author: Henry <akhenry@gmail.com>
Date:   Wed Oct 19 16:30:26 2016 -0700

    [Time Conductor] Implement default sort, fix unpredictable positioning using % left, set TOI on conductor init. #1193

commit d12ae77d95
Author: Henry <akhenry@gmail.com>
Date:   Wed Oct 19 11:29:42 2016 -0700

    Further TOI improvements

commit 22564473b5
Merge: 9f3ec3b 86b51f6
Author: Henry <akhenry@gmail.com>
Date:   Tue Oct 18 16:32:12 2016 -0700

    Merge branch 'open1193' of https://github.com/nasa/openmctweb into open1193

commit 9f3ec3b18f
Author: Henry <akhenry@gmail.com>
Date:   Tue Oct 18 16:31:59 2016 -0700

    Fixed issue with scrolling to row after bounds change

commit 86b51f6cde
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Oct 17 17:19:50 2016 -0700

    [Frontend] Small refactor for mct-include

    Fixes #933
    Fixes #1193
    Markup and CSS tweaked to support using mct-include
    as the main container for the TOI element; TC, plots
    and tables all updated.

commit dadca62955
Author: Henry <akhenry@gmail.com>
Date:   Mon Oct 17 15:54:34 2016 -0700

    Positioning of TOI in tables and plots

commit 7a09bc1339
Author: Henry <akhenry@gmail.com>
Date:   Mon Oct 17 10:11:59 2016 -0700

    Migrated TOI functionality to common controller

commit 6bea6b3bc2
Merge: 660757f 8f67cbd
Author: Henry <akhenry@gmail.com>
Date:   Fri Oct 14 14:38:57 2016 -0700

    Merge branch 'open1193' of https://github.com/nasa/openmctweb into open1193

commit 660757fc1c
Author: Henry <akhenry@gmail.com>
Date:   Fri Oct 14 14:38:46 2016 -0700

    Added TimeOfInterestController

commit 8f67cbd717
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Fri Oct 14 11:33:35 2016 -0700

    [Frontend] Fixed cursor: grab on Time Conductor

    Fixes #933
    Fixes #1193
    Moved cursor: grab into mixin; sass cleanups

commit 07a4e26317
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Fri Oct 14 11:03:23 2016 -0700

    [Frontend] TOI finalizing

    Fixes #933
    Fixes #1193
    HTML template cleaned up; checked in snow theme,
    theme colors tweaked; padding finalized; now uses
    val-to-left instead of -to-right;

commit 271c788f20
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Fri Oct 14 10:22:26 2016 -0700

    [Frontend] TOI in tables

    Fixes #933
    Fixes #1193
    WIP

commit b7e8a1b378
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Oct 13 15:55:48 2016 -0700

    [Frontend] Styling for TOI element

    Fixes #933
    Fixes #1193
    WIP: Table TOI element in progress

commit 6042e4ad58
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Oct 13 13:46:27 2016 -0700

    [Frontend] Styling for TOI element

    Fixes #933
    Fixes #1193
    WIP: plots and TC done, moving on to
    tables; moved sass into conductor-v2;
    moved constants;

commit 1a534301c5
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Oct 13 11:42:10 2016 -0700

    [Frontend] Refactor TOI element

    Fixes #933
    Fixes #1193
    WIP; TOI as mct-include; layout uses
    flex-box; preparing to move TOI sass
    into conductor-v2 directory;

commit 42acf9255e
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Oct 12 18:34:51 2016 -0700

    [Frontend] Adding resync and dedicated unpin buttons

    Fixes #933
    Fixes #1193
    VERY WIP!

commit 3f0eb0b7cb
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Oct 12 18:33:53 2016 -0700

    [Frontend] New glyphs for use by TOI

    Fixes #933
    Fixes #1193
    Symbols font updated

commit 086307ba3a
Author: Henry <akhenry@gmail.com>
Date:   Tue Oct 11 17:16:51 2016 -0700

    Fixed scrolling behavior with TOI

commit 938bf3c4df
Merge: 3910437 8b2047c
Author: Henry <akhenry@gmail.com>
Date:   Tue Oct 11 13:26:20 2016 -0700

    Merge branch 'open1182' into open1193

commit 8b2047ca32
Author: Henry <akhenry@gmail.com>
Date:   Tue Oct 11 13:25:24 2016 -0700

    Fixed issue with setting deltas

commit 3910437033
Merge: 70c4ce2 02c543f
Author: Henry <akhenry@gmail.com>
Date:   Tue Oct 11 12:48:37 2016 -0700

    Merged changes to zoom

commit 02c543fddc
Author: Henry <akhenry@gmail.com>
Date:   Tue Oct 11 12:45:18 2016 -0700

    Fixed zoom in real-time mode

commit 70c4ce24e4
Author: Henry <akhenry@gmail.com>
Date:   Fri Oct 7 17:57:14 2016 -0700

    Added support for clicking row to set TOI

commit b995a8b87b
Merge: 51a9557 b384e84
Author: Henry <akhenry@gmail.com>
Date:   Fri Oct 7 16:11:59 2016 -0700

    Merged from open1182

commit b384e84872
Merge: 5babf72 07140b1
Author: Henry <akhenry@gmail.com>
Date:   Fri Oct 7 15:02:41 2016 -0700

    Merge branch 'open933' into open1182

commit 07140b179f
Merge: 3e9c0eb cbd001e
Author: Henry <akhenry@gmail.com>
Date:   Fri Oct 7 14:54:18 2016 -0700

    Merge branch 'master' into open933

commit 51a95575f7
Author: Henry <akhenry@gmail.com>
Date:   Fri Oct 7 14:51:59 2016 -0700

    [Time Conductor] Refactored time of interest as optional generic behavior of MctTable

commit dfbbc3b0c5
Merge: 430645b 47a0aba
Author: Henry <akhenry@gmail.com>
Date:   Thu Oct 6 10:23:42 2016 -0700

    alt-click to select TOI from table

commit 3e9c0eb7a5
Merge: f1d2072 8a00181
Author: Henry <akhenry@gmail.com>
Date:   Wed Oct 5 12:25:04 2016 -0700

    Merged from master to resolve build issues

commit f1d2072bb9
Author: Henry <akhenry@gmail.com>
Date:   Wed Oct 5 11:17:22 2016 -0700

    Added license information

commit 430645b1f2
Author: Henry <akhenry@gmail.com>
Date:   Wed Oct 5 09:04:13 2016 -0700

    TOI working in time conductor

commit 47a0aba601
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Tue Oct 4 18:12:01 2016 -0700

    [Frontend] TOI sass and markup sanding

    Fixes #933
    Fixes #1193
    Color tweaks; Cleanups, commented
     code removal, etc.; tightened up
     tabular padding and font sizes;

commit 0ed0a48a8c
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Tue Oct 4 15:49:18 2016 -0700

    [Frontend] Styling for TOI element

    Fixes #933
    Fixes #1193
    Tabular styling for TOI;

commit 1650aae518
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Oct 3 15:56:56 2016 -0700

    [Frontend] Styling for TOI element

    Fixes #933
    Fixes #1193
    WIP: Tabular styling for TOI;
    TODO: make bottom border work

commit d3bf6c5857
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Oct 3 15:15:56 2016 -0700

    [Frontend] Styling for TOI element

    Fixes #933
    Fixes #1193
    WIP: Markup and CSS for plots and TC near complete;
    TODO: revised styling in tabular views;

commit 5cd0c8a4fa
Author: Henry <akhenry@gmail.com>
Date:   Fri Sep 30 12:41:14 2016 -0700

    [Time Conductor] merged from open1182

commit 5babf7274d
Author: Henry <akhenry@gmail.com>
Date:   Thu Sep 29 11:21:11 2016 -0700

    [Time Conductor] Tweaked the break points for zoom level indicator

commit 22da34870d
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Sep 29 10:41:21 2016 -0700

    [Frontend] Styling for TOI element

    Fixes #933
    Fixes #1193
    WIP: Markup and CSS revisions for updated UX approach;
    TODO: cosmetic CSS

commit 99253a5904
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Tue Sep 27 20:42:33 2016 -0700

    [Frontend] Styling for TOI element

    Fixes #933
    Fixes #1193
    WIP: Markup and CSS revisions for updated UX approach

commit 2db4aa6235
Author: Henry <akhenry@gmail.com>
Date:   Fri Sep 23 13:06:22 2016 -0700

    [Time Conductor] Added zoom level label

commit bb2ae2f8d1
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Fri Sep 23 11:07:06 2016 -0700

    [Frontend] Styling for TOI element

    Fixes #933
    Fixes #1193
    Finalized TOI in plots, table and TC

commit 0cf4c92555
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Sep 22 19:15:10 2016 -0700

    [Frontend] Styling for TOI element in tables

    Fixes #933
    Fixes #1193
    WIP: styling in tabular mostly done, needs more unit testing
    TODO: fix hide/show for pinned TOI in plots - don't
    show pinned TOI on hover when not .active

commit 3c95c095f1
Author: Henry <akhenry@gmail.com>
Date:   Thu Sep 22 17:22:25 2016 -0700

    [Time Conductor] Refactored out use of angular event bus in favor of making TimeConductorViewService an event emitter.

commit adbcc407ef
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Sep 22 16:06:45 2016 -0700

    [Frontend] Styling for TOI element

    Fixes #933
    Fixes #1193
    WIP: TOI in tables

commit 1c3bd69b66
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Sep 22 16:06:16 2016 -0700

    [Frontend] Styling for TOI element, some refactoring

    Fixes #933
    Fixes #1193
    Moves some TOI styling into dedicated new scss file;
    Enhancements to TOI in plots to bring into parity
    with TOI in TC approach;

commit 49ee5cb74b
Author: Henry <akhenry@gmail.com>
Date:   Thu Sep 22 15:18:48 2016 -0700

    [Time Conductor] Destroy listeners in ConductorAxisController

commit 1a93ba2c3d
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Sep 22 15:09:42 2016 -0700

    [Frontend] Styling for TOI element in TC

    Fixes #933
    Fixes #1193
    Final for now; both types of TOI added to TC markup;
    Hover behavior; constant values in the right places;

commit 98122cc730
Author: Henry <akhenry@gmail.com>
Date:   Tue Sep 20 11:40:58 2016 -0700

    [Time Conductor] Added Zoom

commit 7fcafb6b58
Author: Henry <akhenry@gmail.com>
Date:   Mon Sep 12 16:53:48 2016 -0700

    [Time Conductor] Added pan to Time Conductor

commit d77922d66c
Author: Pete Richards <pete@pete-richards.com>
Date:   Thu Sep 15 09:37:11 2016 -0700

    Revert "[proxyUrl] pass URL parameters to proxied URL"

commit 2e81550c86
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 14 10:16:33 2016 -0700

    Revert "[Build] Check dependencies for vulnerabilities"

commit 8eb7585653
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Mon Aug 29 10:58:02 2016 -0700

    [README] Warn about root installation issues

    Mitigates #1151.

commit 904d56a089
Author: Henry <akhenry@gmail.com>
Date:   Thu Sep 22 13:32:43 2016 -0700

    [Time Conductor] #933 Clean up time conductor listeners on scope destruction

commit c0a96b3c5f
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Sep 22 10:33:05 2016 -0700

    [Frontend] Styling for TOI element in TC

    Fixes #933
    Fixes #1193
    WIP, tweaking TOI in TC

commit 27e6caf69b
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Fri Sep 16 18:51:51 2016 -0700

    [Frontend] Styling for TOI element in plots

    Fixes #933
    Fixes #1193
    Relates to #1182
    WIP, adding TOI to plot view;
    Integrated Andrew's work from #1182;
    Significant changes to markup and
    additions to plot scss;

commit 2c7ae95106
Merge: 2c81b72 41a160f
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Fri Sep 16 15:11:25 2016 -0700

    Merge branch 'open1182' into open1193

commit 41a160fc4f
Merge: 92a80c3 156ba83
Author: Henry <akhenry@gmail.com>
Date:   Fri Sep 16 14:25:49 2016 -0700

    Merge branch 'master' into open1182

commit 92a80c39cb
Author: Henry <akhenry@gmail.com>
Date:   Mon Sep 12 16:53:48 2016 -0700

    [Time Conductor] Added pan to Time Conductor

commit 2c81b72d60
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Sep 15 16:02:30 2016 -0700

    [Frontend] Styling for TOI element in plots

    Fixes #933
    Fixes #1193
    WIP, adding TOI to plot view.

commit 9e2debf801
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Sep 15 15:43:29 2016 -0700

    [Frontend] Styling for TC's TOI element

    Fixes #933
    Fixes #1193
    I lied in my last commit - one more
    alignment tweak needed. Now I'm done.

commit 35872e284c
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Sep 15 15:40:03 2016 -0700

    [Frontend] Styling for TC's TOI element

    Fixes #933
    Fixes #1193
    TOI in TC done for now.

commit 98e67f8dfb
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Thu Sep 15 14:55:06 2016 -0700

    [Frontend] Styling for TC's TOI element

    Fixes #933
    Fixes #1193
    WIP: adjust spacing;
    added pagination buttons

commit f912b9e273
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Sep 14 18:40:55 2016 -0700

    [Frontend] Styling for TC's TOI element

    Fixes #933
    WIP, TC v2 changes

commit 2df1e2b508
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Sep 14 18:40:29 2016 -0700

    [Frontend] Styling for TC's TOI element

    Fixes #933
    WIP, global changes

commit 9e85341aaa
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Sep 14 15:16:16 2016 -0700

    [Frontend] Fixed color for TC clock hands

    Fixes #933

commit 11e06039ec
Merge: a1331b7 f732387
Author: Henry <akhenry@gmail.com>
Date:   Tue Sep 13 13:25:37 2016 -0700

    Merge branch 'master' into open933

commit a1331b7bb3
Merge: d1960b2 e73bb4f
Author: Henry <akhenry@gmail.com>
Date:   Mon Sep 12 15:07:05 2016 -0700

    Merge branch 'master' into open933

commit d1960b2f46
Author: Henry <akhenry@gmail.com>
Date:   Mon Sep 12 14:54:16 2016 -0700

    [Time Conductor] Resolved merge conflicts

commit 9a06325533
Merge: e639e05 4c6ca58
Author: Henry <akhenry@gmail.com>
Date:   Mon Sep 12 14:39:24 2016 -0700

    Merge branch 'master' into open933

commit e639e056ba
Author: Henry <akhenry@gmail.com>
Date:   Fri Sep 9 16:39:21 2016 -0700

    [Time Conductor] Fixing bugs found in smoke testing. Fixes #933

commit fbab890081
Author: Henry <akhenry@gmail.com>
Date:   Wed Sep 7 14:29:21 2016 -0700

    [Time Conductor] Switched conductor to mct-include rather than mct-representation to avoid warnings

commit d37dd52ee1
Merge: 7af5875 58391de
Author: Henry <akhenry@gmail.com>
Date:   Tue Sep 6 16:43:17 2016 -0700

    Merge branch 'master' into open933

commit 7af5875dd5
Author: Henry <akhenry@gmail.com>
Date:   Tue Sep 6 10:04:29 2016 -0700

    [Time Conductor] #933 Fixed code style errors

commit c6eaa3d528
Author: Andrew Henry <andrew.k.henry@nasa.gov>
Date:   Wed Aug 24 15:28:19 2016 +0100

    [Time Conductor] Adding tests and fixing failing ones. #933

commit 4cf6126d35
Author: Henry <akhenry@gmail.com>
Date:   Wed Aug 10 18:22:30 2016 -0700

    Refactoring based on feedback

    Refactoring controller

    Migrating functions off controller onto service class

    Simplified modes

    Adding comments

    Removed unnecessary validation

    Fixing testing issues

commit 4ae6da0334
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Tue Aug 9 17:47:31 2016 -0700

    [Frontend] Data viz in Time Conductor smaller

    Fixes #933
    Reduced height of data viz bar in Time Conductor
    v2;

commit ae39343b76
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Aug 8 12:14:20 2016 -0700

    [Frontend] Fix for bad fix

    Fixes #1112
    Put overflow: hidden back at
    outer wrapper level (now on .t-object.primary-pane
    ) which doens't clip the Inspector expand/collapse;
    did better unit testing;

commit 62ee7e569b
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Aug 8 11:21:01 2016 -0700

    [Frontend] Fix for collapse Inspector button

    Fixes #1112
    Moved min-width and overflow: hidden
    to TC-specific elements; removed
    overflow: hidden from .primary-pane

commit 7557a86208
Merge: 46e644e c8931f8
Author: Henry <akhenry@gmail.com>
Date:   Fri Aug 5 16:37:36 2016 -0700

    Merge branch 'master' into open933

commit 46e644e6dc
Author: Pete Richards <peter.l.richards@nasa.gov>
Date:   Fri Aug 5 14:44:18 2016 -0700

    Use key to retrieve default

commit af7954c5a1
Author: Pete Richards <peter.l.richards@nasa.gov>
Date:   Fri Aug 5 11:24:51 2016 -0700

    Trigger digests when bounds are set

commit 0e0ad64830
Author: Henry <akhenry@gmail.com>
Date:   Thu Aug 4 10:41:58 2016 -0700

    Fixed issue with wrong deltas being applied

commit f3fd386e3b
Author: Henry <akhenry@gmail.com>
Date:   Wed Aug 3 21:03:09 2016 -0700

    Retain time system on mode change

commit 25b9f371e2
Author: Henry <akhenry@gmail.com>
Date:   Wed Aug 3 20:16:03 2016 -0700

    Fixed loss of time system options on navigation

commit 6b482d487b
Merge: f96f78f 9a72c96
Author: Henry <akhenry@gmail.com>
Date:   Wed Aug 3 19:57:03 2016 -0700

    Merged mode-specific defaults, with some refactoring

commit f96f78ff79
Author: Henry <akhenry@gmail.com>
Date:   Wed Aug 3 19:34:31 2016 -0700

    Select appropriate tick source based on mode

commit 579233ade9
Author: Henry <akhenry@gmail.com>
Date:   Wed Aug 3 18:30:01 2016 -0700

    Fixed delta format issue on navigation

commit 9a72c96ea4
Author: Pete Richards <peter.l.richards@nasa.gov>
Date:   Wed Aug 3 18:06:39 2016 -0700

    [TCv2] different defaults by mode

commit f4e1879a2d
Author: Henry <akhenry@gmail.com>
Date:   Wed Aug 3 17:43:07 2016 -0700

    stop listening to tick source on time system change

commit f844495cc1
Author: Henry <akhenry@gmail.com>
Date:   Wed Aug 3 17:40:37 2016 -0700

    Support deltaFormat on timeSystems

commit 900752208f
Author: Pete Richards <peter.l.richards@nasa.gov>
Date:   Wed Aug 3 13:05:43 2016 -0700

    [TCv2] get conductor without service

commit 6501e2eb5f
Author: Henry <akhenry@gmail.com>
Date:   Wed Aug 3 12:22:13 2016 -0700

    Added isUTCBased to TimeSystem interface

commit b9c41107c1
Author: Henry <akhenry@gmail.com>
Date:   Tue Aug 2 22:18:44 2016 -0700

    Time Conductor state retained on navigation

commit 34c62ba405
Author: Henry <akhenry@gmail.com>
Date:   Tue Aug 2 15:16:35 2016 -0700

    Time Conductor in edit mode

commit 1eea5ce480
Merge: 4cd579d 1173828
Author: Henry <akhenry@gmail.com>
Date:   Mon Aug 1 20:29:50 2016 -0700

    merged from master

commit 4cd579d274
Author: Henry <akhenry@gmail.com>
Date:   Mon Aug 1 20:16:46 2016 -0700

    Pass numerical value to format functions

commit 11738286df
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Mon Aug 1 18:56:33 2016 -0700

    [Frontend] Styling for unsynced elements

    Fixes #933

commit ca5206d4a0
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Mon Aug 1 18:55:49 2016 -0700

    [Frontend] Fixing issues with theme coloring

    Fixes #933

commit 573f1f9f99
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Mon Aug 1 18:04:17 2016 -0700

    [Frontend] Hide zoom slider control

    Fixes #933
    Temporarily hiding per request from Andrew
    today;

commit c5c45f0a0e
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Mon Aug 1 18:01:28 2016 -0700

    [Frontend] Update TC2 markup and sass

    Fixes #933
    Update markup and sass in TC2 to be in line with
    updates from master from #1047 glyphs
    to cssclass approach;

commit 121ab413ff
Author: Henry <akhenry@gmail.com>
Date:   Mon Aug 1 17:51:15 2016 -0700

    Apply formatting, filter modes by tick source availability

commit 753bd97c8a
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Mon Aug 1 17:12:44 2016 -0700

    Merge remote-tracking branch 'origin/master' into open933-c

    # Conflicts:
    #	platform/commonUI/edit/res/templates/create/create-menu.html
    #	platform/commonUI/general/res/fonts/symbols/wtdsymbols.eot
    #	platform/commonUI/general/res/fonts/symbols/wtdsymbols.svg
    #	platform/commonUI/general/res/fonts/symbols/wtdsymbols.ttf
    #	platform/commonUI/general/res/fonts/symbols/wtdsymbols.woff
    #	platform/commonUI/general/res/sass/_archetypes.scss
    #	platform/commonUI/general/res/sass/_constants.scss
    #	platform/commonUI/general/res/sass/_icons.scss
    #	platform/commonUI/general/res/sass/_main.scss
    #	platform/commonUI/general/res/sass/_mixins.scss
    #	platform/commonUI/general/res/sass/controls/_buttons.scss
    #	platform/commonUI/general/res/templates/controls/time-controller.html
    #	platform/commonUI/themes/snow/res/sass/_constants.scss

commit fcd7ab93e5
Merge: a75ea67 9b58aa0
Author: Henry <akhenry@gmail.com>
Date:   Mon Aug 1 17:12:00 2016 -0700

    Merge branch 'open933' of https://github.com/nasa/openmctweb into open933

commit c699cb8b4b
Merge: 579c6b6 d1e1ba1
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Mon Aug 1 17:11:37 2016 -0700

    Merge remote-tracking branch 'origin/master' into open933-c

    # Conflicts:
    #	platform/commonUI/edit/res/templates/create/create-menu.html
    #	platform/commonUI/general/res/fonts/symbols/wtdsymbols.eot
    #	platform/commonUI/general/res/fonts/symbols/wtdsymbols.svg
    #	platform/commonUI/general/res/fonts/symbols/wtdsymbols.ttf
    #	platform/commonUI/general/res/fonts/symbols/wtdsymbols.woff
    #	platform/commonUI/general/res/sass/_archetypes.scss
    #	platform/commonUI/general/res/sass/_constants.scss
    #	platform/commonUI/general/res/sass/_icons.scss
    #	platform/commonUI/general/res/sass/_main.scss
    #	platform/commonUI/general/res/sass/_mixins.scss
    #	platform/commonUI/general/res/sass/controls/_buttons.scss
    #	platform/commonUI/general/res/templates/controls/time-controller.html
    #	platform/commonUI/themes/snow/res/sass/_constants.scss

commit a75ea67b8c
Author: Henry <akhenry@gmail.com>
Date:   Mon Aug 1 17:11:01 2016 -0700

    Format updates when time system selected

commit 9b58aa0052
Author: Pete Richards <peter.l.richards@nasa.gov>
Date:   Mon Aug 1 17:03:00 2016 -0700

    [TCv2] Add conductorService for compatibility

    Add conductor service for compatibility with old plugins that depend
    on the conductor service.

commit 579c6b6d24
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Mon Aug 1 16:30:51 2016 -0700

    [Frontend] Styling TC unsynced elements

    Fixes #933
    WIP: Styling for unsynced elements

commit 762f43fa61
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Mon Aug 1 16:26:47 2016 -0700

    [Frontend] Styling TC unsynced elements

    Fixes #933
    WIP: Styling for unsynced elements

commit ce5d0ef5bd
Author: Henry <akhenry@gmail.com>
Date:   Mon Aug 1 16:03:27 2016 -0700

    Merged stylesheet changes

commit 142ee2f336
Merge: 482fcbf 523d674
Author: Henry <akhenry@gmail.com>
Date:   Mon Aug 1 15:44:49 2016 -0700

    Added LocalTimeSystem and merged latest styles

commit 482fcbf6ee
Author: Henry <akhenry@gmail.com>
Date:   Mon Aug 1 15:14:23 2016 -0700

    Refactored bundle

commit 523d6743fb
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Mon Aug 1 12:00:53 2016 -0700

    [Frontend] Added support for thematic styling of Time Conductor v2

    Fixes #933
    Added theme sass files

commit 7af22126d4
Author: Henry <akhenry@gmail.com>
Date:   Wed Jul 27 12:05:03 2016 -0400

    Prevent tabbing into end bounds when not in fixed mode

commit 8e59072537
Author: Henry <akhenry@gmail.com>
Date:   Wed Jul 27 11:55:12 2016 -0400

    Removed LAD and Realtime modes

commit c1bbc4f01d
Author: Henry <akhenry@gmail.com>
Date:   Wed Jul 27 10:38:04 2016 -0400

    Code cleanup

commit aa4a5e56f9
Author: Henry <akhenry@gmail.com>
Date:   Tue Jul 26 16:55:00 2016 -0400

    Improved support from plot

commit 5b2eb72b16
Author: Henry <akhenry@gmail.com>
Date:   Tue Jul 26 08:33:30 2016 -0400

    [Time Conductor] Addressed documentation issues

commit 1b7fc57d21
Author: Henry <akhenry@gmail.com>
Date:   Mon Jul 25 16:55:27 2016 -0400

    Added status support to plots

commit a4f6f6f50b
Author: Henry <akhenry@gmail.com>
Date:   Mon Jul 25 12:09:15 2016 -0400

    Added license

commit 19fd63b850
Merge: c2c8e16 a10ded2
Author: Henry <akhenry@gmail.com>
Date:   Thu Jul 21 20:06:50 2016 -0700

    Merge branch 'open933-frontend-b' into open933

commit c2c8e16453
Author: Henry <akhenry@gmail.com>
Date:   Thu Jul 21 20:03:40 2016 -0700

    Added scale sensitive formatting to UTCTimeFormat

commit a10ded25b4
Merge: da7c636 e6d8944
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 20 18:33:58 2016 -0700

    Merge remote-tracking branch 'origin/open933' into open933-frontend-b

    # Conflicts:
    #	platform/features/conductor-v2/res/sass/time-conductor.scss
    #	platform/features/conductor/res/sass/time-conductor.scss

commit da7c636724
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 20 18:22:20 2016 -0700

    [Frontend] Time Conductor v2 styling

    Fixes #933
    Redo TC icon to use font symbol, added
    new symbol for brackets to font files; font
    anti-aliasing mod for .ui-symbol class;
    layout tweaks; mobile tweaks.

commit b392633bc6
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 20 15:48:22 2016 -0700

    [Frontend] Time Conductor v2 styling

    Fixes #933
    WIP: Significant mobile and desktop style tweaks;
    moved constants into their own include file;

commit ff1678435e
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 20 11:43:40 2016 -0700

    [Frontend] Time Conductor v2 styling

    Fixes #933
    Changed desktop and mobile RT UI to display
    end datetime and hide start;
    WIP: mobile styling for main UI of TC;

commit 2124fe01e1
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 20 10:18:42 2016 -0700

    [Frontend] Renew support for Time Conductor v1

    Fixes #933
    Minor fixes to TCv1 for mobile

commit e6d8944547
Author: Henry <akhenry@gmail.com>
Date:   Tue Jul 19 20:17:06 2016 -0700

    Modified main.js

commit ea1defac28
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Tue Jul 19 18:33:24 2016 -0700

    [Frontend] Renew support for Time Conductor v1

    Fixes #933
    Time Conductors v1 and v2 now build and load their
    own isolated CSS files. All previous styling for TCv1
    should be re-enabled. Note that Conductor v2 mobile
    is not complete yet.

commit 2a19394334
Author: Henry <akhenry@gmail.com>
Date:   Tue Jul 19 19:54:52 2016 -0700

    Added compatibility layer to support existing plots and historical tables

commit f641edbce7
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Tue Jul 19 18:33:24 2016 -0700

    [Frontend] Renew support for Time Conductor v1

    Fixes #933
    Time Conductors v1 and v2 now build and load their
    own isolated CSS files. All previous styling for TCv1
    should be re-enabled. Note that Conductor v2 mobile
    is not complete yet.

commit 15a608a861
Author: Henry <akhenry@gmail.com>
Date:   Mon Jul 18 18:44:29 2016 -0700

    Populate format in input fields

commit 334ca64551
Merge: 0af49ef 4087b9c
Author: Henry <akhenry@gmail.com>
Date:   Mon Jul 18 14:25:02 2016 -0700

    Merged open933-frontend

commit 0af49efe06
Author: Henry <akhenry@gmail.com>
Date:   Mon Jul 18 08:23:27 2016 -0700

    Refactored out modes, time systems, etc.

commit 4087b9cdde
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Fri Jul 15 14:39:29 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    WIP: Fixed look for Firefox

commit 43a804eef4
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Fri Jul 15 07:54:32 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    WIP: Added zoom current range indicator;
    tweaks to style

commit b3a4f52fe2
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Thu Jul 14 18:30:49 2016 -0700

    [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.

commit 671e3016d4
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Thu Jul 14 16:40:05 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    New _animations scss include, moved
    scss around.

commit 379828315f
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Thu Jul 14 16:39:27 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    New _animations scss include, moved
    scss around.

commit 8c5538ec4d
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Thu Jul 14 14:58:18 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    "Sticky" clock anim for LAD mode

commit 2f9fbfef7f
Author: Henry <akhenry@gmail.com>
Date:   Wed Jul 13 20:33:47 2016 -0700

    More refactoring

commit 2baca659ca
Author: Henry <akhenry@gmail.com>
Date:   Wed Jul 13 19:50:58 2016 -0700

    Refactoring

commit 8b694ef337
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 13 19:42:51 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    In-progress: color/size tweaks, fixes for espresso
    theme

commit e193e3dfba
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 13 19:16:27 2016 -0700

    Merge open933 latest, resolve conflicts

    Fixes #933
    Fair amount of manual fixing in time-conductor.html

commit 8214c8e895
Merge: 33b2225 14463d3
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 13 19:16:00 2016 -0700

    Merge open933 latest, resolve conflicts

    Fixes #933
    Fair amount of manual fixing in time-conductor.html

commit 33b2225d10
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 13 18:48:17 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    In-progress: restructured markup to move
    modeModel farther out; animated icon

commit 14463d39a8
Author: Henry <akhenry@gmail.com>
Date:   Wed Jul 13 18:17:27 2016 -0700

    Added end delta

commit fcfda50e73
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 13 15:00:16 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    In-progress: color tweaks, bar sizing,
    field widths

commit 06af84c161
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 13 13:14:32 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    In-progress: fixed SVG text color, field
    styling for fixed vs. real-time, markup cleanup

commit 5238aa2731
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Jul 13 08:07:59 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    In-progress; fixed SVG text color

commit fd29473664
Author: Henry <akhenry@gmail.com>
Date:   Tue Jul 12 15:02:39 2016 -0700

    Support resize

commit 97f3fd516b
Author: Henry <akhenry@gmail.com>
Date:   Tue Jul 12 10:14:26 2016 -0700

    Changed default duration to fifteen minutes

commit 088416905d
Author: Henry <akhenry@gmail.com>
Date:   Tue Jul 12 10:08:08 2016 -0700

    Added duration

commit 2056d87453
Merge: 585da38 64ce8a2
Author: Henry <akhenry@gmail.com>
Date:   Mon Jul 11 14:27:30 2016 -0700

    Merge branch 'open933-frontend' into open933

commit 64ce8a2b2a
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Jul 11 14:03:41 2016 -0700

    [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

commit 585da38a16
Author: Henry <akhenry@gmail.com>
Date:   Mon Jul 11 13:46:46 2016 -0700

    Fixed some merge issues

commit bf0e85a94c
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Jul 11 11:35:26 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #933
    Renamed main class; removed unused <style>
    defs

commit 84b7a9dc2f
Merge: 7b7b21d 11caa83
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Jul 11 11:29:35 2016 -0700

    Merge remote-tracking branch 'origin/open933' into open933-frontend

    Fixes #933
    Conflicts:
    	platform/features/conductor-v2/src/TimeConductorController.js

commit 11caa8396a
Author: Henry <akhenry@gmail.com>
Date:   Mon Jul 11 11:18:23 2016 -0700

    Updated modes

commit 0017b77439
Merge: 5cc81ba 788483e
Author: Henry <akhenry@gmail.com>
Date:   Mon Jul 11 11:06:22 2016 -0700

    Merged markup changes

commit 7b7b21d748
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Jul 11 11:05:47 2016 -0700

    [Frontend] Styling of Time Conductor v2

    Fixes #933
    Tweaks to language; tweak to class name in markup

commit 788483ec13
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Jul 11 10:37:08 2016 -0700

    [Frontend] Styling of Time Conductor v2

    Fixes #933
    Tweaks to language

commit 7b19f91ce6
Merge: 0a0bc55 4e7b69c
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Jul 11 10:31:14 2016 -0700

    [Frontend] Merge latest from open933

    Fixes #933
    Resolve conflicts in
    mode-menu.html, mode-selector.html,
    time-conductor.html; apply tweaks, language, etc.

commit 5cc81ba12a
Author: Henry <akhenry@gmail.com>
Date:   Mon Jul 11 10:26:34 2016 -0700

    [Time Conductor] Added mode class to time conductor

commit 0a0bc55f5f
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Fri Jul 8 17:11:43 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #993
    In-progress; mode menu names and
    descriptors modified, markup cleaned up

commit 4e7b69c4df
Author: Henry <akhenry@gmail.com>
Date:   Fri Jul 8 16:57:50 2016 -0700

    Enabled fixed and real-time modes

commit cf83040c4b
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Fri Jul 8 16:54:49 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #993
    In-progress; Create menu refactoring and new
    mini Create menu

commit 32f7bc86af
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Fri Jul 8 16:54:13 2016 -0700

    [Frontend] Styling for Time Conductor v2

    Fixes #993
    In-progress; class renaming continued,
    cleanups in markup file, in-page styles
    ported to scss

commit e230b92946
Author: Henry <akhenry@gmail.com>
Date:   Fri Jul 8 15:15:12 2016 -0700

    Fixed bug with date selector having to be clicked twice

commit 58ed500ecf
Author: Henry <akhenry@gmail.com>
Date:   Thu Jul 7 16:57:03 2016 -0700

    Time sync via conductor

commit bca5eb0fdb
Author: Henry <akhenry@gmail.com>
Date:   Thu Jul 7 09:47:46 2016 -0700

    [Time Conductor] #933 Initial markup
This commit is contained in:
Pete Richards 2016-11-25 15:02:34 -08:00
parent c98286d426
commit 79b4f9a0f4
65 changed files with 2668 additions and 1001 deletions

View File

@ -99,7 +99,7 @@ define([
"source": "generator",
"domains": [
{
"key": "time",
"key": "utc",
"name": "Time"
},
{

View File

@ -59,7 +59,7 @@ define([
"domains": [
{
"name": "Time",
"key": "timestamp",
"key": "utc",
"format": "utc"
}
]

View File

@ -95,6 +95,45 @@ define([
})[0][0];
}
/**
* Returns a description of the current range of the time conductor's
* bounds.
* @param timeRange
* @returns {*}
*/
UTCTimeFormat.prototype.timeUnits = function (timeRange) {
var momentified = moment.duration(timeRange);
return [
["Decades", function (r) {
return r.years() > 15;
}],
["Years", function (r) {
return r.years() > 1;
}],
["Months", function (r) {
return r.years() === 1 || r.months() > 1;
}],
["Days", function (r) {
return r.months() === 1 || r.days() > 1;
}],
["Hours", function (r) {
return r.days() === 1 || r.hours() > 1;
}],
["Minutes", function (r) {
return r.hours() === 1 || r.minutes() > 1;
}],
["Seconds", function (r) {
return r.minutes() === 1 || r.seconds() > 1;
}],
["Milliseconds", function (r) {
return true;
}]
].filter(function (row) {
return row[1](momentified);
})[0][0];
};
/**
*
* @param value

View File

@ -58,5 +58,26 @@ define([
expect(format.format(APRIL, scale)).toBe("April");
expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016");
});
it("Returns appropriate time units for a given time span", function () {
var ONE_DAY = 1000 * 60 * 60 * 24;
var FIVE_DAYS = 5 * ONE_DAY;
var FIVE_MONTHS = 60 * ONE_DAY;
var ONE_YEAR = 365 * ONE_DAY;
var SEVEN_YEARS = 7 * ONE_YEAR;
var TWO_DECADES = 20 * ONE_YEAR;
//A span of one day should show a zoom label of "Hours"
expect(format.timeUnits(ONE_DAY)).toEqual("Hours");
//Multiple days should display "Days"
expect(format.timeUnits(FIVE_DAYS)).toEqual("Days");
expect(format.timeUnits(FIVE_MONTHS)).toEqual("Days");
//A span of one year should show a zoom level of "Months".
// Multiple years will show "Years"
expect(format.timeUnits(ONE_YEAR)).toEqual("Months");
expect(format.timeUnits(SEVEN_YEARS)).toEqual("Years");
expect(format.timeUnits(TWO_DECADES)).toEqual("Decades");
});
});
});

View File

@ -1,8 +1,8 @@
{
"metadata": {
"name": "openmct-symbols-16px",
"lastOpened": 1479173088107,
"created": 1479173085258
"lastOpened": 1480112601593,
"created": 1480112580248
},
"iconSets": [
{
@ -564,13 +564,21 @@
"code": 921664,
"tempChar": ""
},
{
"order": 120,
"id": 105,
"name": "icon-resync",
"prevSize": 24,
"code": 921655,
"tempChar": ""
},
{
"order": 37,
"prevSize": 24,
"name": "icon-activity",
"id": 32,
"code": 921856,
"tempChar": ""
"tempChar": ""
},
{
"order": 36,
@ -578,7 +586,7 @@
"name": "icon-activity-mode",
"id": 31,
"code": 921857,
"tempChar": ""
"tempChar": ""
},
{
"order": 52,
@ -586,7 +594,7 @@
"name": "icon-autoflow-tabular",
"id": 47,
"code": 921858,
"tempChar": ""
"tempChar": ""
},
{
"order": 55,
@ -594,7 +602,7 @@
"name": "icon-clock",
"id": 50,
"code": 921859,
"tempChar": ""
"tempChar": ""
},
{
"order": 58,
@ -602,7 +610,7 @@
"name": "icon-database",
"id": 53,
"code": 921860,
"tempChar": ""
"tempChar": ""
},
{
"order": 57,
@ -610,7 +618,7 @@
"name": "icon-database-query",
"id": 52,
"code": 921861,
"tempChar": ""
"tempChar": ""
},
{
"order": 17,
@ -618,7 +626,7 @@
"name": "icon-dataset",
"id": 12,
"code": 921862,
"tempChar": ""
"tempChar": ""
},
{
"order": 22,
@ -626,7 +634,7 @@
"name": "icon-datatable",
"id": 17,
"code": 921863,
"tempChar": ""
"tempChar": ""
},
{
"order": 59,
@ -634,7 +642,7 @@
"name": "icon-dictionary",
"id": 54,
"code": 921864,
"tempChar": ""
"tempChar": ""
},
{
"order": 62,
@ -642,7 +650,7 @@
"name": "icon-folder",
"id": 57,
"code": 921865,
"tempChar": ""
"tempChar": ""
},
{
"order": 66,
@ -650,7 +658,7 @@
"name": "icon-image",
"id": 61,
"code": 921872,
"tempChar": ""
"tempChar": ""
},
{
"order": 68,
@ -658,7 +666,7 @@
"name": "icon-layout",
"id": 63,
"code": 921873,
"tempChar": ""
"tempChar": ""
},
{
"order": 77,
@ -666,7 +674,7 @@
"name": "icon-object",
"id": 72,
"code": 921874,
"tempChar": ""
"tempChar": ""
},
{
"order": 78,
@ -674,7 +682,7 @@
"name": "icon-object-unknown",
"id": 73,
"code": 921875,
"tempChar": ""
"tempChar": ""
},
{
"order": 79,
@ -682,7 +690,7 @@
"name": "icon-packet",
"id": 74,
"code": 921876,
"tempChar": ""
"tempChar": ""
},
{
"order": 80,
@ -690,7 +698,7 @@
"name": "icon-page",
"id": 75,
"code": 921877,
"tempChar": ""
"tempChar": ""
},
{
"order": 114,
@ -698,7 +706,7 @@
"name": "icon-plot-overlay",
"prevSize": 24,
"code": 921878,
"tempChar": ""
"tempChar": ""
},
{
"order": 113,
@ -706,7 +714,7 @@
"name": "icon-plot-stacked",
"prevSize": 24,
"code": 921879,
"tempChar": ""
"tempChar": ""
},
{
"order": 10,
@ -714,7 +722,7 @@
"name": "icon-session",
"id": 5,
"code": 921880,
"tempChar": ""
"tempChar": ""
},
{
"order": 24,
@ -722,7 +730,7 @@
"name": "icon-tabular",
"id": 19,
"code": 921881,
"tempChar": ""
"tempChar": ""
},
{
"order": 7,
@ -730,7 +738,7 @@
"name": "icon-tabular-lad",
"id": 2,
"code": 921888,
"tempChar": ""
"tempChar": ""
},
{
"order": 6,
@ -738,7 +746,7 @@
"name": "icon-tabular-lad-set",
"id": 1,
"code": 921889,
"tempChar": ""
"tempChar": ""
},
{
"order": 8,
@ -746,7 +754,7 @@
"name": "icon-tabular-realtime",
"id": 3,
"code": 921890,
"tempChar": ""
"tempChar": ""
},
{
"order": 23,
@ -754,7 +762,7 @@
"name": "icon-tabular-scrolling",
"id": 18,
"code": 921891,
"tempChar": ""
"tempChar": ""
},
{
"order": 112,
@ -762,7 +770,7 @@
"name": "icon-telemetry",
"id": 86,
"code": 921892,
"tempChar": ""
"tempChar": ""
},
{
"order": 90,
@ -770,7 +778,7 @@
"name": "icon-telemetry-panel",
"id": 85,
"code": 921893,
"tempChar": ""
"tempChar": ""
},
{
"order": 93,
@ -778,7 +786,7 @@
"name": "icon-timeline",
"id": 88,
"code": 921894,
"tempChar": ""
"tempChar": ""
},
{
"order": 116,
@ -786,7 +794,7 @@
"name": "icon-timer-v1.5",
"prevSize": 24,
"code": 921895,
"tempChar": ""
"tempChar": ""
},
{
"order": 11,
@ -794,7 +802,7 @@
"name": "icon-topic",
"id": 6,
"code": 921896,
"tempChar": ""
"tempChar": ""
},
{
"order": 115,
@ -802,7 +810,7 @@
"name": "icon-box-with-dashed-lines",
"id": 29,
"code": 921897,
"tempChar": ""
"tempChar": ""
}
],
"metadata": {
@ -1997,7 +2005,7 @@
},
{
"paths": [
"M1024 460.8v-460.8l-175.8 175.8c-85.2-69.6-190.8-107.6-302-107.6-127.6 0-247.6 49.8-338 140s-140 210.4-140 338 49.8 247.6 140 338 210.4 140 338 140 247.6-49.8 338-140c74-74 120.8-167.8 135-269.6h-138.6c-32 155.4-169.8 272.8-334.6 272.8-188.2 0-341.4-153.2-341.4-341.4s153.4-341.2 341.6-341.2c76.8 0 147.6 25.4 204.8 68.2l-187.8 187.8h460.8z"
"M960 432v-432l-164.8 164.8c-79.8-65.2-178.8-100.8-283.2-100.8-119.6 0-232.2 46.6-316.8 131.2s-131.2 197.2-131.2 316.8 46.6 232.2 131.2 316.8c84.6 84.6 197.2 131.2 316.8 131.2s232.2-46.6 316.8-131.2c69.4-69.4 113.2-157.4 126.6-252.8h-130c-29.8 145.8-159 256-313.6 256-176.4 0-320-143.6-320-320s143.8-320 320.2-320c72 0 138.4 23.8 192 64l-176 176h432z"
],
"grid": 16,
"tags": [
@ -2144,6 +2152,37 @@
"1161751207457516161751": []
}
},
{
"id": 105,
"paths": [
"M795.2 164.8c-79.8-65.2-178.8-100.8-283.2-100.8-119.6 0-232.2 46.6-316.8 131.2-69.4 69.4-113.2 157.4-126.6 252.8h130c29.6-145.8 158.8-256 313.4-256 72 0 138.4 23.8 192 64l-176 176h432v-432l-164.8 164.8z",
"M512 832c-72 0-138.4-23.8-192-64l176-176h-432v432l164.8-164.8c79.8 65.2 178.8 100.8 283.2 100.8 119.6 0 232.2-46.6 316.8-131.2 69.4-69.4 113.2-157.4 126.6-252.8h-130c-29.6 145.8-158.8 256-313.4 256z"
],
"attrs": [
{
"fill": "rgb(0, 161, 75)"
},
{
"fill": "rgb(0, 161, 75)"
}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 16,
"tags": [
"icon-resync"
],
"colorPermutations": {
"1161751207457516161751": [
{
"f": 1
},
{
"f": 1
}
]
}
},
{
"paths": [
"M512 0c-282.8 0-512 229.2-512 512s229.2 512 512 512 512-229.2 512-512-229.2-512-512-512zM832 704l-128 128-192-192-192 192-128-128 192-192-192-192 128-128 192 192 192-192 128 128-192 192 192 192z"

View File

@ -65,17 +65,18 @@
<glyph unicode="&#xe1026;" glyph-name="icon-plot-resource" d="M255.884 256c0.040 0.034 0.082 0.074 0.116 0.116v127.884c0 70.58 57.42 128 128 128h255.884c0.040 0.034 0.082 0.074 0.116 0.116v127.884c0 70.58 57.42 128 128 128h143.658c-93.832 117.038-237.98 192-399.658 192-282.77 0-512-229.23-512-512 0-67.904 13.25-132.704 37.256-192h218.628zM768.116 640c-0.040-0.034-0.082-0.074-0.116-0.116v-127.884c0-70.58-57.42-128-128-128h-255.884c-0.040-0.034-0.082-0.074-0.116-0.116v-127.884c0-70.58-57.42-128-128-128h-143.658c93.832-117.038 237.98-192 399.658-192 282.77 0 512 229.23 512 512 0 67.904-13.25 132.704-37.256 192h-218.628z" />
<glyph unicode="&#xe1027;" glyph-name="icon-pointer-left" horiz-adv-x="512" d="M510-64l-256 512 256 512h-256l-256-512 256-512z" />
<glyph unicode="&#xe1028;" glyph-name="icon-pointer-right" horiz-adv-x="512" d="M-2 960l256-512-256-512h256l256 512-256 512z" />
<glyph unicode="&#xe1029;" glyph-name="icon-refresh" d="M1024 499.2v460.8l-175.8-175.8c-85.2 69.6-190.8 107.6-302 107.6-127.6 0-247.6-49.8-338-140s-140-210.4-140-338 49.8-247.6 140-338 210.4-140 338-140 247.6 49.8 338 140c74 74 120.8 167.8 135 269.6h-138.6c-32-155.4-169.8-272.8-334.6-272.8-188.2 0-341.4 153.2-341.4 341.4s153.4 341.2 341.6 341.2c76.8 0 147.6-25.4 204.8-68.2l-187.8-187.8h460.8z" />
<glyph unicode="&#xe1029;" glyph-name="icon-refresh" d="M960 528v432l-164.8-164.8c-79.8 65.2-178.8 100.8-283.2 100.8-119.6 0-232.2-46.6-316.8-131.2s-131.2-197.2-131.2-316.8 46.6-232.2 131.2-316.8c84.6-84.6 197.2-131.2 316.8-131.2s232.2 46.6 316.8 131.2c69.4 69.4 113.2 157.4 126.6 252.8h-130c-29.8-145.8-159-256-313.6-256-176.4 0-320 143.6-320 320s143.8 320 320.2 320c72 0 138.4-23.8 192-64l-176-176h432z" />
<glyph unicode="&#xe1030;" glyph-name="icon-save" d="M192.2 384c-0.2 0-0.2 0 0 0l-0.2-448h640v447.8c0 0 0 0-0.2 0.2h-639.6zM978.8 749.2l-165.4 165.4c-25 25-74.2 45.4-109.4 45.4h-576c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128v448c0 35.2 28.8 64 64 64h640c35.2 0 64-28.8 64-64v-448c70.4 0 128 57.6 128 128v576c0 35.2-20.4 84.4-45.2 109.2zM704 704c0-35.2-28.8-64-64-64h-448c-35.2 0-64 28.8-64 64v192h320v-192h128v192h128v-192z" />
<glyph unicode="&#xe1031;" glyph-name="icon-sine" d="M1022.294 448c-1.746 7.196-3.476 14.452-5.186 21.786-20.036 85.992-53.302 208.976-98 306.538-22.42 48.938-45.298 86.556-69.946 115.006-48.454 55.93-98.176 67.67-131.356 67.67s-82.902-11.74-131.356-67.672c-24.648-28.45-47.528-66.068-69.948-115.006-44.696-97.558-77.962-220.544-98-306.538-21.646-92.898-46.444-175.138-71.71-237.836-16.308-40.46-30.222-66.358-40.6-82.604-10.378 16.246-24.292 42.142-40.6 82.604-23.272 57.75-46.144 132.088-66.524 216.052h-197.362c1.746-7.196 3.476-14.452 5.186-21.786 20.036-85.992 53.302-208.976 98-306.538 22.42-48.938 45.298-86.556 69.946-115.006 48.454-55.932 98.176-67.672 131.356-67.672s82.902 11.74 131.356 67.672c24.648 28.45 47.528 66.068 69.948 115.006 44.696 97.558 77.962 220.544 98 306.538 21.646 92.898 46.444 175.138 71.71 237.836 16.308 40.46 30.222 66.358 40.6 82.604 10.378-16.246 24.292-42.142 40.6-82.604 23.274-57.748 46.146-132.086 66.526-216.050h197.36z" />
<glyph unicode="&#xe1032;" glyph-name="icon-T" d="M0 960v-256h128v64h256v-704h-192v-128h640v128h-192v704h256v-64h128v256z" />
<glyph unicode="&#xe1033;" glyph-name="icon-thumbs-strip" d="M448 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM448 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320z" />
<glyph unicode="&#xe1034;" glyph-name="icon-two-parts-both" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM128 832h320v-768h-320v768zM896 64h-320v768h320v-768z" />
<glyph unicode="&#xe1035;" glyph-name="icon-two-parts-one-only" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM896 64h-320v768h320v-768z" />
<glyph unicode="&#xe1036;" glyph-name="icon-x-in-circle" d="M512 960c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM832 256l-128-128-192 192-192-192-128 128 192 192-192 192 128 128 192-192 192 192 128-128-192-192 192-192z" />
<glyph unicode="&#xe1038;" glyph-name="icon-brightness" d="M253.414 641.939l-155.172 116.384c-50.233-66.209-85.127-146.713-97.91-234.39l191.586-30.216c8.145 56.552 29.998 106.879 62.068 149.006zM191.98 402.283l-191.919-27.434c13.115-90.459 48.009-170.963 99.174-238.453l154.18 117.665c-31.476 41.347-53.309 91.675-61.231 146.504zM466.283 768.020l-27.434 191.919c-90.459-13.115-170.963-48.009-238.453-99.174l117.665-154.18c41.347 31.476 91.675 53.309 146.504 61.231zM822.323 861.758c-66.209 50.233-146.713 85.127-234.39 97.91l-30.216-191.586c56.552-8.145 106.879-29.998 149.006-62.068zM832.020 493.717l191.919 27.434c-13.115 90.459-48.009 170.963-99.174 238.453l-154.18-117.665c31.476-41.347 53.309-91.675 61.231-146.504zM201.677 34.242c66.209-50.233 146.713-85.127 234.39-97.91l30.216 191.586c-56.552 8.145-106.879 29.998-149.006 62.068zM770.586 254.061l155.131-116.343c50.233 66.209 85.127 146.713 97.91 234.39l-191.586 30.216c-8.125-56.564-29.966-106.906-62.028-149.049zM557.717 127.98l27.434-191.919c90.459 13.115 170.963 48.009 238.453 99.174l-117.665 154.18c-41.347-31.476-91.675-53.309-146.504-61.231zM770.586 448c0-142.813-115.773-258.586-258.586-258.586s-258.586 115.773-258.586 258.586c0 142.813 115.773 258.586 258.586 258.586s258.586-115.773 258.586-258.586z" />
<glyph unicode="&#xe1039;" glyph-name="icon-contrast" d="M512 960c-282.78 0-512-229.24-512-512s229.22-512 512-512 512 229.24 512 512-229.22 512-512 512zM783.52 176.48c-69.111-69.481-164.785-112.481-270.502-112.481-0.358 0-0.716 0-1.074 0.001l0.055 768c212.070-0.010 383.982-171.929 383.982-384 0-106.034-42.977-202.031-112.462-271.52z" />
<glyph unicode="&#xe1040;" glyph-name="icon-reset" d="M460.8 499.2l-187.8 187.8c57.2 42.8 128 68.2 204.8 68.2 188.2 0 341.6-153.2 341.6-341.4s-153.2-341.2-341.4-341.2c-165 0-302.8 117.6-334.6 273h-138.4c14.2-101.8 61-195.6 135-269.6 90.2-90.2 210.4-140 338-140s247.6 49.8 338 140 140 210.4 140 338-49.8 247.6-140 338-210.4 140-338 140c-111.4 0-217-38-302-107.6l-176 175.6v-460.8h460.8z" />
<glyph unicode="&#xe1036;" glyph-name="icon-x-in-circle" d="M795.2 795.2c-79.8 65.2-178.8 100.8-283.2 100.8-119.6 0-232.2-46.6-316.8-131.2-69.4-69.4-113.2-157.4-126.6-252.8h130c29.6 145.8 158.8 256 313.4 256 72 0 138.4-23.8 192-64l-176-176h432v432l-164.8-164.8zM512 128c-72 0-138.4 23.8-192 64l176 176h-432v-432l164.8 164.8c79.8-65.2 178.8-100.8 283.2-100.8 119.6 0 232.2 46.6 316.8 131.2 69.4 69.4 113.2 157.4 126.6 252.8h-130c-29.6-145.8-158.8-256-313.4-256z" />
<glyph unicode="&#xe1037;" glyph-name="icon-resync" d="M460.8 499.2l-187.8 187.8c57.2 42.8 128 68.2 204.8 68.2 188.2 0 341.6-153.2 341.6-341.4s-153.2-341.2-341.4-341.2c-165 0-302.8 117.6-334.6 273h-138.4c14.2-101.8 61-195.6 135-269.6 90.2-90.2 210.4-140 338-140s247.6 49.8 338 140 140 210.4 140 338-49.8 247.6-140 338-210.4 140-338 140c-111.4 0-217-38-302-107.6l-176 175.6v-460.8h460.8z" />
<glyph unicode="&#xe1038;" glyph-name="icon-brightness" d="M512 960c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM832 256l-128-128-192 192-192-192-128 128 192 192-192 192 128 128 192-192 192 192 128-128-192-192 192-192z" />
<glyph unicode="&#xe1039;" glyph-name="icon-contrast" d="M253.414 641.939l-155.172 116.384c-50.233-66.209-85.127-146.713-97.91-234.39l191.586-30.216c8.145 56.552 29.998 106.879 62.068 149.006zM191.98 402.283l-191.919-27.434c13.115-90.459 48.009-170.963 99.174-238.453l154.18 117.665c-31.476 41.347-53.309 91.675-61.231 146.504zM466.283 768.020l-27.434 191.919c-90.459-13.115-170.963-48.009-238.453-99.174l117.665-154.18c41.347 31.476 91.675 53.309 146.504 61.231zM822.323 861.758c-66.209 50.233-146.713 85.127-234.39 97.91l-30.216-191.586c56.552-8.145 106.879-29.998 149.006-62.068zM832.020 493.717l191.919 27.434c-13.115 90.459-48.009 170.963-99.174 238.453l-154.18-117.665c31.476-41.347 53.309-91.675 61.231-146.504zM201.677 34.242c66.209-50.233 146.713-85.127 234.39-97.91l30.216 191.586c-56.552 8.145-106.879 29.998-149.006 62.068zM770.586 254.061l155.131-116.343c50.233 66.209 85.127 146.713 97.91 234.39l-191.586 30.216c-8.125-56.564-29.966-106.906-62.028-149.049zM557.717 127.98l27.434-191.919c90.459 13.115 170.963 48.009 238.453 99.174l-117.665 154.18c-41.347-31.476-91.675-53.309-146.504-61.231zM770.586 448c0-142.813-115.773-258.586-258.586-258.586s-258.586 115.773-258.586 258.586c0 142.813 115.773 258.586 258.586 258.586s258.586-115.773 258.586-258.586z" />
<glyph unicode="&#xe1040;" glyph-name="icon-reset" d="M512 960c-282.78 0-512-229.24-512-512s229.22-512 512-512 512 229.24 512 512-229.22 512-512 512zM783.52 176.48c-69.111-69.481-164.785-112.481-270.502-112.481-0.358 0-0.716 0-1.074 0.001l0.055 768c212.070-0.010 383.982-171.929 383.982-384 0-106.034-42.977-202.031-112.462-271.52z" />
<glyph unicode="&#xe1100;" glyph-name="icon-activity" d="M576 896h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" />
<glyph unicode="&#xe1101;" glyph-name="icon-activity-mode" d="M512 960c-214.866 0-398.786-132.372-474.744-320h90.744c56.86 0 107.938-24.724 143.094-64h240.906l-192 192h256l320-320-320-320h-256l192 192h-240.906c-35.156-39.276-86.234-64-143.094-64h-90.744c75.958-187.628 259.878-320 474.744-320 282.77 0 512 229.23 512 512s-229.23 512-512 512z" />
<glyph unicode="&#xe1102;" glyph-name="icon-autoflow-tabular" d="M192 960c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 960h256v-1024h-256v1024zM832 960h-64v-704h256v512c0 105.6-86.4 192-192 192z" />

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -78,7 +78,7 @@ $treeContextTriggerW: 20px;
/*************** Tabular */
$tabularHeaderH: 22px;
$tabularTdPadLR: $itemPadLR;
$tabularTdPadTB: 3px;
$tabularTdPadTB: 2px;
/*************** Imagery */
$imageMainControlBarH: 25px;
$imageThumbsD: 120px;
@ -99,7 +99,7 @@ $plotXBarH: 32px;
$plotLegendH: 20px;
$plotSwatchD: 8px;
// 1: Top, 2: right, 3: bottom, 4: left
$plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH + $interiorMargin, $plotYBarW);
$plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH, $plotYBarW);
/* min plot height is based on user testing to find minimum useful height */
$plotMinH: 95px;
/*************** Bubbles */

View File

@ -191,6 +191,19 @@ a.disabled {
overflow-y: auto;
}
.slidable {
cursor: move; // Fallback
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
&.horz {
cursor: col-resize;
}
&.vert {
cursor: row-resize;
}
}
.no-margin {
margin: 0;
}

View File

@ -77,6 +77,7 @@ $glyph-icon-x-in-circle: '\e1036';
$glyph-icon-brightness: '\e1038';
$glyph-icon-contrast: '\e1039';
$glyph-icon-reset: '\e1040';
$glyph-icon-resync: '\e1037';
$glyph-icon-activity: '\e1100';
$glyph-icon-activity-mode: '\e1101';
$glyph-icon-autoflow-tabular: '\e1102';
@ -179,6 +180,7 @@ $glyph-icon-box-with-dashed-lines: '\e1129';
.icon-brightness { @include glyph($glyph-icon-brightness); }
.icon-contrast { @include glyph($glyph-icon-contrast); }
.icon-reset { @include glyph($glyph-icon-reset); }
.icon-resync { @include glyph($glyph-icon-resync); }
.icon-activity { @include glyph($glyph-icon-activity); }
.icon-activity-mode { @include glyph($glyph-icon-activity-mode); }
.icon-autoflow-tabular { @include glyph($glyph-icon-autoflow-tabular); }
@ -215,4 +217,4 @@ $glyph-icon-box-with-dashed-lines: '\e1129';
.icon-eye-open-12px { @include glyph($glyph-icon-eye-open,'symbolsfont-12px'); }
.icon-collapse-pane-left-12px { @include glyph($glyph-icon-collapse-pane-left,'symbolsfont-12px'); }
.icon-collapse-pane-right-12px { @include glyph($glyph-icon-collapse-pane-right,'symbolsfont-12px'); }
.icon-folder-12px { @include glyph($glyph-icon-folder,'symbolsfont-12px'); }
.icon-folder-12px { @include glyph($glyph-icon-folder,'symbolsfont-12px'); }

View File

@ -363,14 +363,13 @@
}
}
@mixin webkitProp($name, $val) {
#{$name}: #{$val};
-webkit-#{$name}: #{$val};
}
@mixin webkitVal($name, $val) {
#{$name}: #{$val};
#{$name}: -webkit-#{$val};
@mixin cursorGrab() {
cursor: grab;
cursor: -webkit-grab;
&:active {
cursor: grabbing;
cursor: -webkit-grabbing;
}
}
@mixin verticalCenter {
@ -392,6 +391,14 @@
white-space: nowrap;
}
@mixin reverseEllipsis() {
direction: rtl;
unicode-bidi:bidi-override;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
@mixin scrollH($showBar: auto) {
overflow-x: $showBar;
overflow-y: hidden;

View File

@ -661,3 +661,4 @@ body.desktop {
background: $scrollbarTrackColorBg;
}
}

View File

@ -51,13 +51,9 @@ table {
tbody, .tbody {
display: table-row-group;
tr, .tr {
&:hover {
background: rgba($colorTabBodyFg, 0.1);
}
}
}
tr, .tr {
border-top: 1px solid $colorTabBorder;
display: table-row;
&:first-child .td {
border-top: none;
@ -71,11 +67,12 @@ table {
}
th, .th, td, .td {
display: table-cell;
font-size: 0.7rem;
}
th, .th {
border-left: 1px solid $colorTabHeaderBorder;
color: $colorTabHeaderFg;
padding: $tabularTdPadLR $tabularTdPadLR;
padding: $tabularTdPadTB $tabularTdPadLR;
white-space: nowrap;
vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default
&:first-child {
@ -99,7 +96,6 @@ table {
}
}
td, .td {
border-bottom: 1px solid $colorTabBorder;
min-width: 20px;
color: $colorTelemFresh;
padding: $tabularTdPadTB $tabularTdPadLR;

View File

@ -46,18 +46,42 @@
}
}
.gl-plot-wrapper-display-area-and-x-axis {
// Holds the plot area and the X-axis only
position: absolute;
top: nth($plotDisplayArea, 1);
right: nth($plotDisplayArea, 2);
bottom: 0;
left: nth($plotDisplayArea, 4);
.gl-plot-display-area {
//@include test(yellow);
@if $colorPlotBg != none {
background-color: $colorPlotBg;
}
position: absolute;
top: 0;
right: 0;
bottom: nth($plotDisplayArea, 3);
left: 0;
cursor: crosshair;
border: 1px solid $colorPlotAreaBorder;
}
.gl-plot-axis-area.gl-plot-x {
//@include test(green);
top: auto;
right: 0;
bottom: 0;
left: 0;
height: $plotXBarH;
width: auto;
overflow: hidden;
}
}
.gl-plot-axis-area {
//@include test();
position: absolute;
&.gl-plot-x {
top: auto;
right: 0;
bottom: $interiorMargin;
left: $plotYBarW;
height: $plotXBarH;
width: auto;
overflow: hidden;
}
&.gl-plot-y {
top: $plotLegendH + $interiorMargin;
right: auto;
@ -84,19 +108,6 @@
}
}
.gl-plot-display-area {
@if $colorPlotBg != none {
background-color: $colorPlotBg;
}
position: absolute;
top: nth($plotDisplayArea, 1);
right: nth($plotDisplayArea, 2);
bottom: nth($plotDisplayArea, 3);
left: nth($plotDisplayArea, 4);
cursor: crosshair;
border: 1px solid $colorPlotAreaBorder;
}
.gl-plot-label,
.l-plot-label {
color: $colorPlotLabelFg;
@ -265,13 +276,9 @@
.gl-plot-tick,
.tick-label {
direction: rtl;
unicode-bidi:bidi-override;
@include reverseEllipsis();
font-size: 0.7rem;
position: absolute;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
&.gl-plot-x-tick-label,
&.tick-label-x {
right: auto;

View File

@ -62,7 +62,7 @@ $colorCreateBtn: $colorKey;
$colorGridLines: rgba(#fff, 0.05);
$colorInvokeMenu: #fff;
$colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: pullForward($colorObjHdrTxt, 20%);
$colorObjHdrIc: lighten($colorObjHdrTxt, 20%);
$colorTick: rgba(white, 0.2);
$colorSelectableSelectedPrimary: $colorKey;
$colorSelectableSelectedSecondary: pushBack($colorSelectableSelectedPrimary, 20%);
@ -158,11 +158,11 @@ $shdwItemText: rgba(black, 0.1) 0 1px 2px;
$colorItemBgSelected: $colorKey;
// Tabular
$colorTabBorder: pullForward($colorBodyBg, 10%);
$colorTabBorder: pullForward($colorBodyBg, 5%);
$colorTabBodyBg: darken($colorBodyBg, 10%);
$colorTabBodyFg: lighten($colorTabBodyBg, 40%);
$colorTabHeaderBg: rgba(white, 0.1); // lighten($colorBodyBg, 10%);
$colorTabHeaderFg: $colorBodyFg; //lighten($colorTabHeaderBg, 40%);
$colorTabHeaderBg: rgba(white, 0.1);
$colorTabHeaderFg: $colorBodyFg;
$colorTabHeaderBorder: $colorBodyBg;
// Plot

View File

@ -62,7 +62,7 @@ $colorCreateBtn: $colorKey;
$colorGridLines: rgba(#000, 0.05);
$colorInvokeMenu: #fff;
$colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: pushBack($colorObjHdrTxt, 30%);
$colorObjHdrIc: lighten($colorObjHdrTxt, 30%);
$colorTick: rgba(black, 0.2);
$colorSelectableSelectedPrimary: $colorKey;
$colorSelectableSelectedSecondary: pushBack($colorSelectableSelectedPrimary, 20%);

View File

@ -23,31 +23,20 @@
define([
"./src/ConductorTelemetryDecorator",
"./src/ConductorRepresenter",
"./src/ConductorService",
'legacyRegistry'
], function (
ConductorTelemetryDecorator,
ConductorRepresenter,
ConductorService,
legacyRegistry
) {
legacyRegistry.register("platform/features/conductor-v2/compatibility", {
"extensions": {
"services": [
{
"key": "conductorService",
"implementation": ConductorService,
"depends": [
"timeConductor"
]
}
],
"representers": [
{
"implementation": ConductorRepresenter,
"depends": [
"timeConductor"
"openmct"
]
}
],
@ -57,7 +46,7 @@ define([
"provides": "telemetryService",
"implementation": ConductorTelemetryDecorator,
"depends": [
"timeConductor"
"openmct"
]
}
]

View File

@ -37,11 +37,11 @@ define(
* @constructor
*/
function ConductorRepresenter(
timeConductor,
openmct,
scope,
element
) {
this.conductor = timeConductor;
this.conductor = openmct.conductor;
this.scope = scope;
this.element = element;

View File

@ -1,72 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
], function (
) {
function Conductor(timeConductorService) {
this.timeConductor = timeConductorService.conductor();
}
Conductor.prototype.displayStart = function () {
return this.timeConductor.bounds().start;
};
Conductor.prototype.displayEnd = function () {
return this.timeConductor.bounds().end;
};
Conductor.prototype.domainOptions = function () {
throw new Error([
'domainOptions not implemented in compatibility layer,',
' you must be using some crazy unknown code'
].join(''));
};
Conductor.prototype.domain = function () {
var system = this.timeConductor.timeSystem();
return {
key: system.metadata.key,
name: system.metadata.name,
format: system.formats()[0]
};
};
/**
* Small compatibility layer that implements old conductor service by
* wrapping new time conductor. This allows views that previously depended
* directly on the conductor service to continue to do so without
* modification.
*/
function ConductorService(timeConductor) {
this.tc = new Conductor(timeConductor);
}
ConductorService.prototype.getConductor = function () {
return this.tc;
};
return ConductorService;
});

View File

@ -36,8 +36,8 @@ define(
* the service which exposes the global time conductor
* @param {TelemetryService} telemetryService the decorated service
*/
function ConductorTelemetryDecorator(timeConductor, telemetryService) {
this.conductor = timeConductor;
function ConductorTelemetryDecorator(openmct, telemetryService) {
this.conductor = openmct.conductor;
this.telemetryService = telemetryService;
this.amendRequests = ConductorTelemetryDecorator.prototype.amendRequests.bind(this);

View File

@ -23,37 +23,39 @@
define([
"./src/ui/TimeConductorViewService",
"./src/ui/TimeConductorController",
"./src/TimeConductor",
"./src/ui/ConductorAxisController",
"./src/ui/ConductorTOIController",
"./src/ui/TimeOfInterestController",
"./src/ui/MctConductorAxis",
"./src/ui/NumberFormat",
"text!./res/templates/time-conductor.html",
"text!./res/templates/mode-selector/mode-selector.html",
"text!./res/templates/mode-selector/mode-menu.html",
'legacyRegistry'
"text!./res/templates/time-of-interest.html",
"legacyRegistry"
], function (
TimeConductorViewService,
TimeConductorController,
TimeConductor,
ConductorAxisController,
ConductorTOIController,
TimeOfInterestController,
MCTConductorAxis,
NumberFormat,
timeConductorTemplate,
modeSelectorTemplate,
modeMenuTemplate,
timeOfInterest,
legacyRegistry
) {
legacyRegistry.register("platform/features/conductor-v2/conductor", {
"extensions": {
"services": [
{
"key": "timeConductor",
"implementation": TimeConductor
},
{
"key": "timeConductorViewService",
"implementation": TimeConductorViewService,
"depends": [
"timeConductor",
"openmct",
"timeSystems[]"
]
}
@ -65,9 +67,29 @@ define([
"depends": [
"$scope",
"$window",
"timeConductor",
"openmct",
"timeConductorViewService",
"timeSystems[]"
"timeSystems[]",
"formatService"
]
},
{
"key": "ConductorTOIController",
"implementation": ConductorTOIController,
"depends": [
"$scope",
"openmct",
"timeConductorViewService",
"formatService"
]
},
{
"key": "TimeOfInterestController",
"implementation": TimeOfInterestController,
"depends": [
"$scope",
"openmct",
"formatService"
]
}
],
@ -76,7 +98,7 @@ define([
"key": "mctConductorAxis",
"implementation": MCTConductorAxis,
"depends": [
"timeConductor",
"openmct",
"formatService"
]
}
@ -103,6 +125,10 @@ define([
{
"key": "mode-selector",
"template": modeSelectorTemplate
},
{
"key": "time-of-interest",
"template": timeOfInterest
}
],
"representations": [

View File

@ -1,3 +1,11 @@
$ueTimeConductorH: (25px, 3px, 20px);
$ueTimeConductorH: (25px, 16px, 20px); // Heights for Ticks, Data Visualization, Controls elements
$ueTimeConductorRtH: (25px, 3px, 20px); // Heights for elements in Real-time mode
$timeCondInputTimeSysDefW: 165px; // Default width for datetime value inputs
$timeCondInputDeltaDefW: 60px; // Default width for delta value inputs, typically 00:00:00
$timeCondInputDeltaDefW: 60px; // Default width for delta value inputs, typically 00:00:00
$timeCondTOIIconD: 12px; // height and width of icon used for TOI indicator
$timeCondTOIValOffset: 0px;
$ticksBlockerFadeW: 50px;
$toiBlockerFadeW: 10px;
$toiH: 12px; // Needs to be an even number to avoid sub-pixel antialiasing of the vertical line
$toiPad: 4px;
$timeCondAxisLROffset: (($toiH / 2) + $toiPad, ($toiH / 2) + $toiPad); // Margin to left, right of tick axis and vis bar. For paging, use 15, 20px

View File

@ -54,7 +54,7 @@
// Clock hands
div[class*="hand"] {
$handW: 2px;
$handH: $d * 0.4; //8px;
$handH: $d * 0.4;
@include transform(translate(-50%, -50%));
@include animation-iteration-count(infinite);
@include animation-timing-function(linear);
@ -125,7 +125,6 @@
}
.l-time-conductor-inputs-holder {
$ticksBlockerFadeW: 50px;
$iconCalendarW: 16px;
$wBgColor: $colorBodyBg;
@ -136,6 +135,7 @@
bottom: 0;
left: 0;
z-index: 1;
pointer-events: none;
.l-time-range-w {
// Wraps a datetime text input field
height: 100%;
@ -159,6 +159,9 @@
content: 'End';
}
}
.l-time-conductor-inputs {
pointer-events: auto;
}
input[type="text"] {
@include trans-prop-nice(padding, 250ms);
}
@ -175,7 +178,7 @@
}
.l-time-conductor-inputs-and-ticks {
$c: $colorTimeCondTicks; //$colorTick;
$c: $colorTimeCondTicks;
height: $r1H;
mct-conductor-axis {
display: block;
@ -184,8 +187,9 @@
}
.l-axis-holder {
height: $r1H;
position: relative;
width: 100%;
position: absolute;
left: nth($timeCondAxisLROffset, 1);
right: nth($timeCondAxisLROffset, 2);
svg {
text-rendering: geometricPrecision;
width: 100%;
@ -208,9 +212,49 @@
}
}
}
.l-data-visualization {
background: $colorTimeCondDataVisBg;
.l-data-visualization-holder {
height: $r2H;
z-index: 2; // Must lift above ticks and inputs
.l-page-button,
.l-data-visualization {
position: absolute;
top: 0;
bottom: 0;
}
.l-page-button {
@if nth($timeCondAxisLROffset, 1) + nth($timeCondAxisLROffset, 2) > 30 {
left: 0;
width: nth($timeCondAxisLROffset, 1);
&.align-right {
left: auto;
right: 0;
width: nth($timeCondAxisLROffset, 2);
}
} @else {
// Hide these if the offsets aren't enough
display: none;
}
}
.l-data-visualization {
background: $colorTimeCondDataVisBg;
left: nth($timeCondAxisLROffset, 1);
right: nth($timeCondAxisLROffset, 2);
&:hover {
.l-toi-holder.hover {
opacity: 1;
}
.l-toi-holder.pinned.active {
opacity: 0.4;
.l-toi-val {
pointer-events: none;
opacity: 0;
}
}
}
}
}
.l-time-conductor-controls {
@ -219,13 +263,11 @@
.l-time-conductor-zoom-w {
@include justify-content(flex-end);
.time-conductor-zoom {
display: none; // TEMP per request from Andrew 8/1/16
height: $r3H;
min-width: 100px;
width: 20%;
}
.time-conductor-zoom-current-range {
display: none; // TEMP per request from Andrew 8/1/16
color: $colorTick;
}
}
@ -235,7 +277,9 @@
&.realtime-mode,
&.lad-mode {
.time-conductor-icon {
&:before { color: $colorTimeCondKeyBg; }
&:before {
color: $colorTimeCondKeyBg;
}
div[class*="hand"] {
@include animation-name(clock-hands);
&:before {
@ -276,13 +320,16 @@
}
.l-data-visualization {
background: $colorTimeCondDataVisRtBg !important
background: $colorTimeCondDataVisRtBg !important;
}
.mode-selector .s-menu-button {
$fg: $colorTimeCondKeyFg;
@include btnSubtle($bg: $colorTimeCondKeyBg, $bgHov: pullForward($colorTimeCondKeyBg, $ltGamma), $fg: $colorTimeCondKeyFg);
&:before { color: $fg !important; };
&:before {
color: $fg !important;
}
;
color: $fg !important;
}
}
@ -298,6 +345,9 @@
.mode-selector .s-menu-button:before {
content: $i;
}
.l-axis-holder {
@include cursorGrab();
}
}
// Realtime mode
@ -344,7 +394,9 @@
/******************************************************************** MOBILE */
@include phoneandtablet {
.l-time-conductor-holder { min-width: 0 !important; }
.l-time-conductor-holder {
min-width: 0 !important;
}
.super-menu.mini {
width: 200px;
height: 100px;

View File

@ -0,0 +1,271 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
mct-include.l-toi-holder,
.l-toi-holder:after,
.l-toi-holder:before {
display: block;
position: absolute;
}
mct-include.l-toi-holder {
$blockerFadeW: $toiBlockerFadeW;
@include transform(translateX(-50%));
color: $toiColorBg;
position: absolute;
top: 0;
bottom: 0;
width: $toiH;
&:not(.pinned) {
display: none;
}
&.pinned {
display: block;
}
&:before,
&:after {
// Vertical lines. TC uses both; plot only uses :before
border-left: 1px dashed $toiColorBg;
box-sizing: border-box;
content: '';
display: block;
left: 50%;
position: absolute;
top: 0;
bottom: 0;
width: 2px;
z-index: 2;
}
.l-toi {
// Holds buttons and val. Acts as a blocking element.
@include background-image(linear-gradient(90deg, transparent, $toiColorBlocker 10%, $toiColorBlocker 90%, transparent 100%));
position: absolute;
align-items: center;
box-sizing: content-box;
height: $toiH;
left: $toiPad * -2;
@include transform(translateY(-50%)); top: 50%;
padding: $toiPad;
z-index: 1;
.l-toi-buttons {
@include trans-prop-nice($props: (width, padding), $dur: 250ms);
border-radius: $controlCr;
box-sizing: content-box;
font-size: $toiH;
height: 100%;
line-height: $toiH;
padding: $toiPad;
overflow: hidden;
white-space: nowrap;
justify-content: space-between;
width: $toiH;
&:hover {
// Expand and display controls; clock icon changes to resync
background-color: $toiColorBg;
cursor: pointer;
width: 30px;
.icon-button {
color: rgba($toiColorCtrlFg, 0.5);
opacity: 1;
&:hover {
color: $toiColorCtrlFg;
}
}
.t-button-resync {
order: 1;
&:before {
content: $glyph-icon-resync;
}
}
.t-button-unpin {
order: 2;
&:hover {
color: $toiColorBgAlert;
}
}
& + .l-toi-val {
// Dim the value to emphasize the controls
opacity: 0.5;
}
}
}
.icon-button {
color: $toiColorBg;
}
.t-button-resync {
@extend .icon-clock;
&:hover { color: $toiColorCtrlFg; }
}
.t-button-unpin {
@include trans-prop-nice($props: opacity, $dur: 150ms);
@extend .icon-x-in-circle;
float: right;
opacity: 0;
}
}
.l-toi-val {
display: none; // Hide by default; see .show-val below
}
// TOI is showing value as well
&.show-val {
.l-toi {
.l-toi-buttons {
order: 1;
&:hover {
margin-right: $interiorMarginSm;
}
}
.l-toi-val {
@include trans-prop-nice($props: opacity, $dur: 150ms);
background-color: $toiColorBg;
border-radius: $controlCr;
box-sizing: content-box;
color: $toiColorFg;
display: inline-block;
font-size: 0.7rem;
font-weight: 400;
height: $toiH;
line-height: $toiH;
order: 2;
padding: 1px 3px;
white-space: nowrap;
}
}
&.val-to-left {
.l-toi {
left: auto;
right: $toiPad * -2;
.l-toi-buttons {
order: 2;
&:hover {
.t-button-resync { order: 2; }
.t-button-unpin { order: 1; }
margin-left: $interiorMarginSm;
}
}
.l-toi-val {
order: 1;
}
}
}
}
}
// TOI in tables
.tabular,
table {
tbody, .tbody {
tr, .tr {
&.l-toi-tablerow {
border-top: 1px dashed $toiColorBg;
z-index: 1;
td, .td {
.l-toi-holder {
left: 50% !important;
&:before,
&:after {
display: none;
}
.l-toi {
background: rgba($toiColorBlocker, 0.9);
border-radius: 20%;
height: auto;
padding: $toiPad;
@include transform(translate(-50%, -50%));
left: 50%; right: auto; top: 0;
.l-toi-buttons {
padding: 1px;
&:hover {
padding: $toiPad;
}
}
}
}
}
}
}
}
}
// TOI in plots
.gl-plot {
.gl-plot-wrapper-display-area-and-x-axis {
right: nth($plotDisplayArea, 2) + ($toiH / 2) + $toiPad; // Make room for TOI element
.l-toi-holder {
bottom: nth($plotDisplayArea, 3) - $interiorMargin;
z-index: 3;
.l-toi {
@include transform(translateY(100%));
}
.l-toi {
top: auto;
bottom: $toiPad;
}
}
}
}
// TOI in Time Conductor
.l-time-conductor {
.l-toi-holder {
$linesVOffset: 2px;
&:before,
&:after {
// Vertical lines
border-left-style: solid;
height: ((nth($ueTimeConductorH, 2) - $timeCondTOIIconD)/2) + $linesVOffset;
}
&:before {
@include transform(translate(-50%, $linesVOffset * -1));
top: 0px;
bottom: auto;
}
&:after {
@include transform(translate(-50%, $linesVOffset));
top: auto;
bottom: 0px;
}
}
}

View File

@ -34,6 +34,17 @@
$colorTimeCondTicks: pullForward($colorBodyBg, 30%);
$colorTimeCondKeyBg: #4e70dc;
$colorTimeCondKeyFg: #fff;
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 10%);
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 5%);
$colorTimeCondDataVisRtBg: pushBack($colorTimeCondKeyBg, 10%);
@import "time-conductor-base";
// Time of Interest
$toiColorBg: #6b93c6;
$toiColorBlocker: $colorBodyBg; // Color of blocker element beneath the TOI icons
$toiColorFg: #000; // Used by value display
$toiColorCtrlFg: #fff;
$toiColorBgAlert: #cf2a12; // Used by unpin button on hover
$colorTimeCondTOIBg: darken($toiColorBg, 20%);
$colorTimeCondTOIBgHov: $toiColorBg;
@import "time-conductor-base";
@import "time-of-interest";

View File

@ -36,4 +36,15 @@ $colorTimeCondKeyBg: #6178dc;
$colorTimeCondKeyFg: #fff;
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 10%);
$colorTimeCondDataVisRtBg: pushBack($colorTimeCondKeyBg, 30%);
@import "time-conductor-base";
// Time of Interest
$toiColorBg: #6b93c6;
$toiColorBlocker: $colorBodyBg; // Color of blocker element beneath the TOI icons
$toiColorFg: #fff; // Used by value display
$toiColorCtrlFg: #fff;
$toiColorBgAlert: #ff9540; // Used by unpin button on hover
$colorTimeCondTOIBg: darken($toiColorBg, 20%);
$colorTimeCondTOIBgHov: $toiColorBg;
@import "time-conductor-base";
@import "time-of-interest";

View File

@ -1,6 +1,7 @@
<!-- 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.selectedKey}}-mode {{timeSystemModel.selected.metadata.key}}-time-system">
class="holder grows flex-elem l-flex-row l-time-conductor {{modeModel.selectedKey}}-mode {{timeSystemModel.selected.metadata.key}}-time-system"
ng-class="{'status-panning': tcController.panning}">
<div class="flex-elem holder time-conductor-icon">
<div class="hand-little"></div>
@ -13,63 +14,67 @@
<form class="l-time-conductor-inputs-holder"
ng-submit="tcController.updateBoundsFromForm(boundsModel)">
<span class="l-time-range-w start-w">
<span class="l-time-range-input-w start-date">
<span class="title"></span>
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.format,
validate: tcController.validation.validateStart
}"
ng-model="boundsModel"
ng-blur="tcController.updateBoundsFromForm(boundsModel)"
field="'start'"
class="time-range-input">
</mct-control>
</span>
<span class="l-time-range-input-w time-delta start-delta"
ng-class="{'hide':(modeModel.selectedKey === 'fixed')}">
-
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.deltaFormat,
validate: tcController.validation.validateStartDelta
}"
ng-model="boundsModel"
ng-blur="tcController.updateDeltasFromForm(boundsModel)"
field="'startDelta'"
class="hrs-min-input">
</mct-control>
<span class="l-time-conductor-inputs">
<span class="l-time-range-input-w start-date">
<span class="title"></span>
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.format,
validate: tcController.validation.validateStart
}"
ng-model="boundsModel"
ng-blur="tcController.updateBoundsFromForm(boundsModel)"
field="'start'"
class="time-range-input">
</mct-control>
</span>
<span class="l-time-range-input-w time-delta start-delta"
ng-class="{'hide':(modeModel.selectedKey === 'fixed')}">
-
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.deltaFormat,
validate: tcController.validation.validateStartDelta
}"
ng-model="boundsModel"
ng-blur="tcController.updateDeltasFromForm(boundsModel)"
field="'startDelta'"
class="hrs-min-input">
</mct-control>
</span>
</span>
</span>
<span class="l-time-range-w end-w">
<span class="l-time-range-input-w end-date"
ng-controller="ToggleController as t2">
<span class="title"></span>
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.format,
validate: tcController.validation.validateEnd
}"
ng-model="boundsModel"
ng-blur="tcController.updateBoundsFromForm(boundsModel)"
ng-disabled="modeModel.selectedKey !== 'fixed'"
field="'end'"
class="time-range-input">
</mct-control>
</span>
<span class="l-time-range-input-w time-delta end-delta"
ng-class="{'hide':(modeModel.selectedKey === 'fixed')}">
+
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.deltaFormat,
validate: tcController.validation.validateEndDelta
}"
ng-model="boundsModel"
ng-blur="tcController.updateDeltasFromForm(boundsModel)"
field="'endDelta'"
class="hrs-min-input">
</mct-control>
<span class="l-time-conductor-inputs">
<span class="l-time-range-input-w end-date"
ng-controller="ToggleController as t2">
<span class="title"></span>
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.format,
validate: tcController.validation.validateEnd
}"
ng-model="boundsModel"
ng-blur="tcController.updateBoundsFromForm(boundsModel)"
ng-disabled="modeModel.selectedKey !== 'fixed'"
field="'end'"
class="time-range-input">
</mct-control>
</span>
<span class="l-time-range-input-w time-delta end-delta"
ng-class="{'hide':(modeModel.selectedKey === 'fixed')}">
+
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.deltaFormat,
validate: tcController.validation.validateEndDelta
}"
ng-model="boundsModel"
ng-blur="tcController.updateDeltasFromForm(boundsModel)"
field="'endDelta'"
class="hrs-min-input">
</mct-control>
</span>
</span>
</span>
@ -78,8 +83,18 @@
<mct-conductor-axis></mct-conductor-axis>
</div>
<!-- Holds data availability, time of interest -->
<div class="l-data-visualization l-row-elem flex-elem"></div>
<!-- Holds data visualization, time of interest -->
<div class="l-data-visualization-holder l-row-elem flex-elem"
ng-controller="ConductorTOIController as toi">
<a class="l-page-button s-icon-button icon-pointer-left"></a>
<div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">
<mct-include key="'time-of-interest'"
class="l-toi-holder show-val"
ng-class="{ pinned: toi.pinned, 'val-to-left': toi.left > 80 }"
ng-style="{'left': toi.left + '%'}"></mct-include>
</div>
<a class="l-page-button align-right s-icon-button icon-pointer-right"></a>
</div>
<!-- Holds time system and session selectors, and zoom control -->
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
@ -98,9 +113,18 @@
}">
</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"></span>
<input class="time-conductor-zoom flex-elem" type="range" />
<div ng-if="tcController.supportsZoom"
class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
{{currentZoom}}
<span
class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span>
<input class="time-conductor-zoom flex-elem" type="range"
ng-model="tcController.currentZoom"
ng-mouseUp="tcController.onZoomStop(tcController.currentZoom)"
ng-change="tcController.onZoom(tcController.currentZoom)"
min="0.01"
step="0.01"
max="0.99" />
</div>
</div>

View File

@ -0,0 +1,12 @@
<div class="abs angular-controller"
ng-controller="TimeOfInterestController as toi">
<div class="l-flex-row l-toi">
<span class="flex-elem l-flex-row l-toi-buttons">
<a class="flex-elem t-button-resync icon-button" title="Re-sync Time of Interest"
ng-click="toi.resync()"></a>
<a class="flex-elem t-button-unpin icon-button" title="Unset Time of Interest"
ng-click="toi.dismiss()"></a>
</span>
<span class="flex-elem l-toi-val">{{toi.toiText}}</span>
</div>
</div>

View File

@ -1,179 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['EventEmitter'], function (EventEmitter) {
/**
* The public API for setting and querying time conductor state. The
* time conductor is the means by which the temporal bounds of a view
* are controlled. Time-sensitive views will typically respond to
* changes to bounds or other properties of the time conductor and
* update the data displayed based on the time conductor state.
*
* The TimeConductor extends the EventEmitter class. A number of events are
* fired when properties of the time conductor change, which are
* documented below.
* @constructor
*/
function TimeConductor() {
EventEmitter.call(this);
//The Time System
this.system = undefined;
//The Time Of Interest
this.toi = undefined;
this.boundsVal = {
start: undefined,
end: undefined
};
//Default to fixed mode
this.followMode = false;
}
TimeConductor.prototype = Object.create(EventEmitter.prototype);
/**
* Validate the given bounds. This can be used for pre-validation of
* 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
*/
TimeConductor.prototype.validateBounds = function (bounds) {
if ((bounds.start === undefined) ||
(bounds.end === undefined) ||
isNaN(bounds.start) ||
isNaN(bounds.end)
) {
return "Start and end must be specified as integer values";
} else if (bounds.start > bounds.end) {
return "Specified start date exceeds end bound";
}
return true;
};
/**
* Get or set the follow mode of the time conductor. In follow mode the
* time conductor ticks, regularly updating the bounds from a timing
* source appropriate to the selected time system and mode of the time
* conductor.
* @fires TimeConductor#follow
* @param {boolean} followMode
* @returns {boolean}
*/
TimeConductor.prototype.follow = function (followMode) {
if (arguments.length > 0) {
this.followMode = followMode;
/**
* @event TimeConductor#follow The TimeConductor has toggled
* into or out of follow mode.
* @property {boolean} followMode true if follow mode is
* enabled, otherwise false.
*/
this.emit('follow', this.followMode);
}
return this.followMode;
};
/**
* @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.
*/
/**
* Get or set the start and end time of the time conductor. Basic validation
* of bounds is performed.
*
* @param {TimeConductorBounds} newBounds
* @throws {Error} Validation error
* @fires TimeConductor#bounds
* @returns {TimeConductorBounds}
*/
TimeConductor.prototype.bounds = function (newBounds) {
if (arguments.length > 0) {
var validationResult = this.validateBounds(newBounds);
if (validationResult !== true) {
throw new Error(validationResult);
}
//Create a copy to avoid direct mutation of conductor bounds
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
/**
* @event TimeConductor#bounds The start time, end time, or
* both have been updated
* @property {TimeConductorBounds} bounds
*/
this.emit('bounds', this.boundsVal);
}
//Return a copy to prevent direct mutation of time conductor bounds.
return JSON.parse(JSON.stringify(this.boundsVal));
};
/**
* Get or set the time system of the TimeConductor. Time systems determine
* 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 {TimeConductorBounds} bounds
* @fires TimeConductor#timeSystem
* @returns {TimeSystem} The currently applied time system
*/
TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) {
if (arguments.length >= 2) {
this.system = newTimeSystem;
/**
* @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
* @property {TimeSystem} The value of the currently applied
* Time System
* */
this.emit('timeSystem', this.system);
this.bounds(bounds);
} else if (arguments.length === 1) {
throw new Error('Must set bounds when changing time system');
}
return this.system;
};
/**
* 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 TimeConductor#timeOfInterest
* @param newTOI
* @returns {number} the current time of interest
*/
TimeConductor.prototype.timeOfInterest = function (newTOI) {
if (arguments.length > 0) {
this.toi = newTOI;
/**
* @event TimeConductor#timeOfInterest The Time of Interest has moved.
* @property {number} Current time of interest
*/
this.emit('timeOfInterest', this.toi);
}
return this.toi;
};
return TimeConductor;
});

View File

@ -1,110 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./TimeConductor'], function (TimeConductor) {
describe("The Time Conductor", function () {
var tc,
timeSystem,
bounds,
eventListener,
toi,
follow;
beforeEach(function () {
tc = new TimeConductor();
timeSystem = {};
bounds = {start: 0, end: 0};
eventListener = jasmine.createSpy("eventListener");
toi = 111;
follow = true;
});
it("Supports setting and querying of time of interest and and follow mode", function () {
expect(tc.timeOfInterest()).not.toBe(toi);
tc.timeOfInterest(toi);
expect(tc.timeOfInterest()).toBe(toi);
expect(tc.follow()).not.toBe(follow);
tc.follow(follow);
expect(tc.follow()).toBe(follow);
});
it("Allows setting of valid bounds", function () {
bounds = {start: 0, end: 1};
expect(tc.bounds()).not.toEqual(bounds);
expect(tc.bounds.bind(tc, bounds)).not.toThrow();
expect(tc.bounds()).toEqual(bounds);
});
it("Disallows setting of invalid bounds", function () {
bounds = {start: 1, end: 0};
expect(tc.bounds()).not.toEqual(bounds);
expect(tc.bounds.bind(tc, bounds)).toThrow();
expect(tc.bounds()).not.toEqual(bounds);
bounds = {start: 1};
expect(tc.bounds()).not.toEqual(bounds);
expect(tc.bounds.bind(tc, bounds)).toThrow();
expect(tc.bounds()).not.toEqual(bounds);
});
it("Allows setting of time system with bounds", function () {
expect(tc.timeSystem()).not.toBe(timeSystem);
expect(tc.timeSystem.bind(tc, timeSystem, bounds)).not.toThrow();
expect(tc.timeSystem()).toBe(timeSystem);
});
it("Disallows setting of time system without bounds", function () {
expect(tc.timeSystem()).not.toBe(timeSystem);
expect(tc.timeSystem.bind(tc, timeSystem)).toThrow();
expect(tc.timeSystem()).not.toBe(timeSystem);
});
it("Emits an event when time system changes", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("timeSystem", eventListener);
tc.timeSystem(timeSystem, bounds);
expect(eventListener).toHaveBeenCalledWith(timeSystem);
});
it("Emits an event when time of interest changes", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("timeOfInterest", eventListener);
tc.timeOfInterest(toi);
expect(eventListener).toHaveBeenCalledWith(toi);
});
it("Emits an event when bounds change", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("bounds", eventListener);
tc.bounds(bounds);
expect(eventListener).toHaveBeenCalledWith(bounds);
});
it("Emits an event when follow mode changes", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("follow", eventListener);
tc.follow(follow);
expect(eventListener).toHaveBeenCalledWith(follow);
});
});
});

View File

@ -73,8 +73,22 @@ define([], function () {
throw new Error('Not implemented');
};
/**
/***
*
* @typedef {object} TimeConductorZoom
* @property {number} min The largest time span that the time
* conductor can display in this time system. ie. the span of the time
* conductor in its most zoomed out state.
* @property {number} max The smallest time span that the time
* conductor can display in this time system. ie. the span of the time
* conductor bounds in its most zoomed in state.
*
* @typedef {object} TimeSystemDefault
* @property {TimeConductorDeltas} deltas The deltas to apply by default
* when this time system is active. Applies to real-time modes only
* @property {TimeConductorBounds} bounds The bounds to apply by default
* when this time system is active
* @property {TimeConductorZoom} zoom Default min and max zoom levels
* @returns {TimeSystemDefault[]} At least one set of default values for
* this time system.
*/

View File

@ -0,0 +1,240 @@
/*****************************************************************************
* 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(
[
"d3"
],
function (d3) {
var PADDING = 1;
/**
* Controller that renders a horizontal time scale spanning the current bounds defined in the time conductor.
* Used by the mct-conductor-axis directive
* @constructor
*/
function ConductorAxisController(openmct, formatService, conductorViewService, scope, element) {
// Dependencies
this.formatService = formatService;
this.conductor = openmct.conductor;
this.conductorViewService = conductorViewService;
this.scope = scope;
this.initialized = false;
this.bounds = this.conductor.bounds();
this.timeSystem = this.conductor.timeSystem();
//Bind all class functions to 'this'
Object.keys(ConductorAxisController.prototype).filter(function (key) {
return typeof ConductorAxisController.prototype[key] === 'function';
}).forEach(function (key) {
this[key] = ConductorAxisController.prototype[key].bind(this);
}.bind(this));
this.initialize(element);
}
/**
* @private
*/
ConductorAxisController.prototype.destroy = function () {
this.conductor.off('timeSystem', this.changeTimeSystem);
this.conductor.off('bounds', this.changeBounds);
this.conductorViewService.off("zoom", this.onZoom);
this.conductorViewService.off("zoom-stop", this.onZoomStop);
};
/**
* @private
*/
ConductorAxisController.prototype.initialize = function (element) {
this.target = element[0].firstChild;
var height = this.target.offsetHeight;
var vis = d3.select(this.target)
.append("svg:svg")
.attr("width", "100%")
.attr("height", height);
this.xAxis = d3.axisTop();
// draw x axis with labels and move to the bottom of the chart area
this.axisElement = vis.append("g")
.attr("transform", "translate(0," + (height - PADDING) + ")");
if (this.timeSystem !== undefined) {
this.changeTimeSystem(this.timeSystem);
this.setScale();
}
//Respond to changes in conductor
this.conductor.on("timeSystem", this.changeTimeSystem);
this.conductor.on("bounds", this.changeBounds);
this.scope.$on("$destroy", this.destroy);
this.conductorViewService.on("zoom", this.onZoom);
this.conductorViewService.on("zoom-stop", this.onZoomStop);
};
/**
* @private
*/
ConductorAxisController.prototype.changeBounds = function (bounds) {
this.bounds = bounds;
if (!this.zooming) {
this.setScale();
}
};
/**
* Set the scale of the axis, based on current conductor bounds.
*/
ConductorAxisController.prototype.setScale = function () {
var width = this.target.offsetWidth;
var timeSystem = this.conductor.timeSystem();
var bounds = this.bounds;
if (timeSystem.isUTCBased()) {
this.xScale = this.xScale || d3.scaleUtc();
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
} else {
this.xScale = this.xScale || d3.scaleLinear();
this.xScale.domain([bounds.start, bounds.end]);
}
this.xAxis.scale(this.xScale);
this.xScale.range([PADDING, width - PADDING * 2]);
this.axisElement.call(this.xAxis);
this.msPerPixel = (bounds.end - bounds.start) / width;
};
/**
* When the time system changes, update the scale and formatter used for showing times.
* @param timeSystem
*/
ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) {
this.timeSystem = timeSystem;
var key = timeSystem.formats()[0];
if (key !== undefined) {
var format = this.formatService.getFormat(key);
var bounds = this.conductor.bounds();
//The D3 scale used depends on the type of time system as d3
// supports UTC out of the box.
if (timeSystem.isUTCBased()) {
this.xScale = d3.scaleUtc();
} else {
this.xScale = d3.scaleLinear();
}
this.xAxis.scale(this.xScale);
//Define a custom format function
this.xAxis.tickFormat(function (tickValue) {
// Normalize date representations to numbers
if (tickValue instanceof Date) {
tickValue = tickValue.getTime();
}
return format.format(tickValue, {
min: bounds.start,
max: bounds.end
});
});
this.axisElement.call(this.xAxis);
}
};
/**
* The user has stopped panning the time conductor scale element.
* @event panStop
*/
/**
* Called on release of mouse button after dragging the scale left or right.
* @fires platform.features.conductor.ConductorAxisController~panStop
*/
ConductorAxisController.prototype.panStop = function () {
//resync view bounds with time conductor bounds
this.conductorViewService.emit("pan-stop");
this.conductor.bounds(this.bounds);
};
/**
* Rescales the axis when the user zooms. Although zoom ultimately results in a bounds change once the user
* releases the zoom slider, dragging the slider will not immediately change the conductor bounds. It will
* however immediately update the scale and the bounds displayed in the UI.
* @private
* @param {ZoomLevel}
*/
ConductorAxisController.prototype.onZoom = function (zoom) {
this.zooming = true;
this.bounds = zoom.bounds;
this.setScale();
};
/**
* @private
*/
ConductorAxisController.prototype.onZoomStop = function (zoom) {
this.zooming = false;
};
/**
* @event platform.features.conductor.ConductorAxisController~pan
* Fired when the time conductor is panned
*/
/**
* Initiate panning via a click + drag gesture on the time conductor
* scale. Panning triggers a "pan" event
* @param {number} delta the offset from the original click event
* @see TimeConductorViewService#
* @fires platform.features.conductor.ConductorAxisController~pan
*/
ConductorAxisController.prototype.pan = function (delta) {
if (!this.conductor.follow()) {
var deltaInMs = delta[0] * this.msPerPixel;
var bounds = this.conductor.bounds();
var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000;
var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
this.bounds = {
start: start,
end: end
};
this.setScale();
this.conductorViewService.emit("pan", this.bounds);
}
};
/**
* Invoked on element resize. Will rebuild the scale based on the new dimensions of the element.
*/
ConductorAxisController.prototype.resize = function () {
this.setScale();
};
return ConductorAxisController;
}
);

View File

@ -20,16 +20,33 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./MctConductorAxis'], function (MctConductorAxis) {
describe("The MctConductorAxis directive", function () {
var directive,
define([
'./ConductorAxisController',
'zepto',
'd3'
], function (
ConductorAxisController,
$,
d3
) {
describe("The ConductorAxisController", function () {
var controller,
mockConductor,
mockConductorViewService,
mockFormatService,
mockScope,
mockElement,
mockTarget,
mockBounds,
d3;
element,
mockTimeSystem,
mockFormat;
function getCallback(target, name) {
return target.calls.filter(function (call) {
return call.args[0] === name;
})[0].args[1];
}
beforeEach(function () {
mockScope = jasmine.createSpyObj("scope", [
@ -52,7 +69,8 @@ define(['./MctConductorAxis'], function (MctConductorAxis) {
"timeSystem",
"bounds",
"on",
"off"
"off",
"follow"
]);
mockConductor.bounds.andReturn(mockBounds);
@ -60,87 +78,101 @@ define(['./MctConductorAxis'], function (MctConductorAxis) {
"getFormat"
]);
var d3Functions = [
"scale",
"scaleUtc",
"scaleLinear",
"select",
"append",
"attr",
"axisTop",
"call",
"tickFormat",
"domain",
"range"
];
d3 = jasmine.createSpyObj("d3", d3Functions);
d3Functions.forEach(function (func) {
d3[func].andReturn(d3);
});
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
"on",
"off",
"emit"
]);
directive = new MctConductorAxis(mockConductor, mockFormatService);
directive.d3 = d3;
directive.link(mockScope, [mockElement]);
spyOn(d3, 'scaleUtc').andCallThrough();
spyOn(d3, 'scaleLinear').andCallThrough();
element = $('<div style="width: 100px;"><div style="width: 100%;"></div></div>');
$(document).find('body').append(element);
controller = new ConductorAxisController({conductor: mockConductor}, mockFormatService, mockConductorViewService, mockScope, element);
mockTimeSystem = jasmine.createSpyObj("timeSystem", [
"formats",
"isUTCBased"
]);
mockFormat = jasmine.createSpyObj("format", [
"format"
]);
mockTimeSystem.formats.andReturn(["mockFormat"]);
mockFormatService.getFormat.andReturn(mockFormat);
mockConductor.timeSystem.andReturn(mockTimeSystem);
mockTimeSystem.isUTCBased.andReturn(false);
});
it("listens for changes to time system and bounds", function () {
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", directive.changeTimeSystem);
expect(mockConductor.on).toHaveBeenCalledWith("bounds", directive.setScale);
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
expect(mockConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
});
it("on scope destruction, deregisters listeners", function () {
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", directive.destroy);
directive.destroy();
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", directive.changeTimeSystem);
expect(mockConductor.off).toHaveBeenCalledWith("bounds", directive.setScale);
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
controller.destroy();
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
});
describe("when the time system changes", function () {
var mockTimeSystem;
var mockFormat;
beforeEach(function () {
mockTimeSystem = jasmine.createSpyObj("timeSystem", [
"formats",
"isUTCBased"
]);
mockFormat = jasmine.createSpyObj("format", [
"format"
]);
mockTimeSystem.formats.andReturn(["mockFormat"]);
mockFormatService.getFormat.andReturn(mockFormat);
});
it("uses a UTC scale for UTC time systems", function () {
mockTimeSystem.isUTCBased.andReturn(true);
directive.changeTimeSystem(mockTimeSystem);
controller.changeTimeSystem(mockTimeSystem);
expect(d3.scaleUtc).toHaveBeenCalled();
expect(d3.scaleLinear).not.toHaveBeenCalled();
});
it("uses a linear scale for non-UTC time systems", function () {
mockTimeSystem.isUTCBased.andReturn(false);
directive.changeTimeSystem(mockTimeSystem);
controller.changeTimeSystem(mockTimeSystem);
expect(d3.scaleLinear).toHaveBeenCalled();
expect(d3.scaleUtc).not.toHaveBeenCalled();
});
it("sets axis domain to time conductor bounds", function () {
mockTimeSystem.isUTCBased.andReturn(false);
mockConductor.timeSystem.andReturn(mockTimeSystem);
directive.setScale();
expect(d3.domain).toHaveBeenCalledWith([mockBounds.start, mockBounds.end]);
controller.setScale();
expect(controller.xScale.domain()).toEqual([mockBounds.start, mockBounds.end]);
});
it("uses the format specified by the time system to format tick" +
" labels", function () {
directive.changeTimeSystem(mockTimeSystem);
expect(d3.tickFormat).toHaveBeenCalled();
d3.tickFormat.mostRecentCall.args[0]();
controller.changeTimeSystem(mockTimeSystem);
expect(mockFormat.format).toHaveBeenCalled();
});
it('responds to zoom events', function () {
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", controller.onZoom);
var cb = getCallback(mockConductorViewService.on, "zoom");
spyOn(controller, 'setScale').andCallThrough();
cb({bounds: {start: 0, end: 100}});
expect(controller.setScale).toHaveBeenCalled();
});
it('adjusts scale on pan', function () {
spyOn(controller, 'setScale').andCallThrough();
controller.pan(100);
expect(controller.setScale).toHaveBeenCalled();
});
it('emits event on pan', function () {
spyOn(controller, 'setScale').andCallThrough();
controller.pan(100);
expect(mockConductorViewService.emit).toHaveBeenCalledWith("pan", jasmine.any(Object));
});
it('cleans up listeners on destruction', function () {
controller.destroy();
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", controller.onZoom);
});
});
});
});

View File

@ -0,0 +1,124 @@
/*****************************************************************************
* 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"],
function ($) {
/**
* Controller for the Time of Interest indicator in the conductor itself. Sets the horizontal position of the
* TOI indicator based on the current value of the TOI, and the width of the TOI conductor.
* @memberof platform.features.conductor
*/
function ConductorTOIController($scope, openmct, conductorViewService) {
this.conductor = openmct.conductor;
this.conductorViewService = conductorViewService;
//Bind all class functions to 'this'
Object.keys(ConductorTOIController.prototype).filter(function (key) {
return typeof ConductorTOIController.prototype[key] === 'function';
}).forEach(function (key) {
this[key] = ConductorTOIController.prototype[key].bind(this);
}.bind(this));
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
this.conductorViewService.on('zoom', this.setOffsetFromZoom);
this.conductorViewService.on('pan', this.setOffsetFromBounds);
var timeOfInterest = this.conductor.timeOfInterest();
if (timeOfInterest) {
this.changeTimeOfInterest(timeOfInterest);
}
$scope.$on('$destroy', this.destroy);
}
/**
* @private
*/
ConductorTOIController.prototype.destroy = function () {
this.conductor.off('timeOfInterest', this.changeTimeOfInterest);
this.conductorViewService.off('zoom', this.setOffsetFromZoom);
this.conductorViewService.off('pan', this.setOffsetFromBounds);
};
/**
* Given some bounds, set horizontal position of TOI indicator based
* on current conductor TOI value. Bounds are provided so that
* ephemeral bounds from zoom and pan events can be used as well
* as current conductor bounds, allowing TOI to be updated in
* realtime during scroll and zoom.
* @param {TimeConductorBounds} bounds
*/
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
var toi = this.conductor.timeOfInterest();
if (toi !== undefined) {
var offset = toi - bounds.start;
var duration = bounds.end - bounds.start;
this.left = offset / duration * 100;
this.pinned = true;
} else {
this.left = 0;
this.pinned = false;
}
};
/**
* @private
*/
ConductorTOIController.prototype.setOffsetFromZoom = function (zoom) {
return this.setOffsetFromBounds(zoom.bounds);
};
/**
* Invoked when time of interest changes. Will set the horizontal offset of the TOI indicator.
* @private
*/
ConductorTOIController.prototype.changeTimeOfInterest = function () {
var bounds = this.conductor.bounds();
if (bounds) {
this.setOffsetFromBounds(bounds);
}
};
/**
* On a mouse click event within the TOI element, convert position within element to a time of interest, and
* set the time of interest on the conductor.
* @param e The angular $event object
*/
ConductorTOIController.prototype.setTOIFromPosition = function (e) {
//TOI is set using the alt key modified + primary click
if (e.altKey) {
var element = $(e.currentTarget);
var width = element.width();
var relativeX = e.pageX - element.offset().left;
var percX = relativeX / width;
var bounds = this.conductor.bounds();
var timeRange = bounds.end - bounds.start;
this.conductor.timeOfInterest(timeRange * percX + bounds.start);
}
};
return ConductorTOIController;
}
);

View File

@ -0,0 +1,153 @@
/*****************************************************************************
* 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([
'./ConductorTOIController'
], function (
ConductorTOIController
) {
var mockConductor;
var mockConductorViewService;
var mockScope;
var mockAPI;
var conductorTOIController;
function getNamedCallback(thing, name) {
return thing.calls.filter(function (call) {
return call.args[0] === name;
}).map(function (call) {
return call.args;
})[0][1];
}
describe("The ConductorTOIController", function () {
beforeEach(function () {
mockConductor = jasmine.createSpyObj("conductor", [
"bounds",
"timeOfInterest",
"on",
"off"
]);
mockAPI = {conductor: mockConductor};
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
"on",
"off"
]);
mockScope = jasmine.createSpyObj("openMCT", [
"$on"
]);
conductorTOIController = new ConductorTOIController(mockScope, mockAPI, mockConductorViewService);
});
it("listens to changes in the time of interest on the conductor", function () {
expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", jasmine.any(Function));
});
describe("when responding to changes in the time of interest", function () {
var toiCallback;
beforeEach(function () {
var bounds = {
start: 0,
end: 200
};
mockConductor.bounds.andReturn(bounds);
toiCallback = getNamedCallback(mockConductor.on, "timeOfInterest");
});
it("calculates the correct horizontal offset based on bounds and current TOI", function () {
//Expect time of interest position to be 50% of element width
mockConductor.timeOfInterest.andReturn(100);
toiCallback();
expect(conductorTOIController.left).toBe(50);
//Expect time of interest position to be 25% of element width
mockConductor.timeOfInterest.andReturn(50);
toiCallback();
expect(conductorTOIController.left).toBe(25);
//Expect time of interest position to be 0% of element width
mockConductor.timeOfInterest.andReturn(0);
toiCallback();
expect(conductorTOIController.left).toBe(0);
//Expect time of interest position to be 100% of element width
mockConductor.timeOfInterest.andReturn(200);
toiCallback();
expect(conductorTOIController.left).toBe(100);
});
it("renders the TOI indicator visible", function () {
expect(conductorTOIController.pinned).toBeFalsy();
mockConductor.timeOfInterest.andReturn(100);
toiCallback();
expect(conductorTOIController.pinned).toBe(true);
});
});
it("responds to zoom events", function () {
var mockZoom = {
bounds: {
start: 500,
end: 1000
}
};
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", jasmine.any(Function));
// Should correspond to horizontal offset of 50%
mockConductor.timeOfInterest.andReturn(750);
var zoomCallback = getNamedCallback(mockConductorViewService.on, "zoom");
zoomCallback(mockZoom);
expect(conductorTOIController.left).toBe(50);
});
it("responds to pan events", function () {
var mockPanBounds = {
start: 1000,
end: 3000
};
expect(mockConductorViewService.on).toHaveBeenCalledWith("pan", jasmine.any(Function));
// Should correspond to horizontal offset of 25%
mockConductor.timeOfInterest.andReturn(1500);
var panCallback = getNamedCallback(mockConductorViewService.on, "pan");
panCallback(mockPanBounds);
expect(conductorTOIController.left).toBe(25);
});
it("Cleans up all listeners when controller destroyed", function () {
var zoomCB = getNamedCallback(mockConductorViewService.on, "zoom");
var panCB = getNamedCallback(mockConductorViewService.on, "pan");
var toiCB = getNamedCallback(mockConductor.on, "timeOfInterest");
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
getNamedCallback(mockScope.$on, "$destroy")();
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", zoomCB);
expect(mockConductorViewService.off).toHaveBeenCalledWith("pan", panCB);
expect(mockConductor.off).toHaveBeenCalledWith("timeOfInterest", toiCB);
});
});
});

View File

@ -20,127 +20,35 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[
"d3"
],
function (d3) {
var PADDING = 1;
define(['./ConductorAxisController'], function (ConductorAxisController) {
function MctConductorAxis() {
/**
* The mct-conductor-axis renders a horizontal axis with regular
* labelled 'ticks'. It requires 'start' and 'end' integer values to
* be specified as attributes.
*/
function MCTConductorAxis(conductor, formatService) {
// Dependencies
this.d3 = d3;
this.conductor = conductor;
this.formatService = formatService;
// Runtime properties (set by 'link' function)
this.target = undefined;
this.xScale = undefined;
this.xAxis = undefined;
this.axisElement = undefined;
return {
controller: [
'openmct',
'formatService',
'timeConductorViewService',
'$scope',
'$element',
ConductorAxisController
],
controllerAs: 'axis',
// Angular Directive interface
this.link = this.link.bind(this);
this.restrict = "E";
this.template =
"<div class=\"l-axis-holder\" mct-resize=\"resize()\"></div>";
this.priority = 1000;
restrict: 'E',
priority: 1000,
//Bind all class functions to 'this'
Object.keys(MCTConductorAxis.prototype).filter(function (key) {
return typeof MCTConductorAxis.prototype[key] === 'function';
}).forEach(function (key) {
this[key] = this[key].bind(this);
}.bind(this));
}
MCTConductorAxis.prototype.setScale = function () {
var width = this.target.offsetWidth;
var timeSystem = this.conductor.timeSystem();
var bounds = this.conductor.bounds();
if (timeSystem.isUTCBased()) {
this.xScale = this.xScale || this.d3.scaleUtc();
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
} else {
this.xScale = this.xScale || this.d3.scaleLinear();
this.xScale.domain([bounds.start, bounds.end]);
}
this.xScale.range([PADDING, width - PADDING * 2]);
this.axisElement.call(this.xAxis);
};
MCTConductorAxis.prototype.changeTimeSystem = function (timeSystem) {
var key = timeSystem.formats()[0];
if (key !== undefined) {
var format = this.formatService.getFormat(key);
var bounds = this.conductor.bounds();
if (timeSystem.isUTCBased()) {
this.xScale = this.d3.scaleUtc();
} else {
this.xScale = this.d3.scaleLinear();
}
this.xAxis.scale(this.xScale);
//Define a custom format function
this.xAxis.tickFormat(function (tickValue) {
// Normalize date representations to numbers
if (tickValue instanceof Date) {
tickValue = tickValue.getTime();
}
return format.format(tickValue, {
min: bounds.start,
max: bounds.end
});
});
this.axisElement.call(this.xAxis);
}
};
MCTConductorAxis.prototype.destroy = function () {
this.conductor.off('timeSystem', this.changeTimeSystem);
this.conductor.off('bounds', this.setScale);
};
MCTConductorAxis.prototype.link = function (scope, element) {
var conductor = this.conductor;
this.target = element[0].firstChild;
var height = this.target.offsetHeight;
var vis = this.d3.select(this.target)
.append('svg:svg')
.attr('width', '100%')
.attr('height', height);
this.xAxis = this.d3.axisTop();
// draw x axis with labels and move to the bottom of the chart area
this.axisElement = vis.append("g")
.attr("transform", "translate(0," + (height - PADDING) + ")");
scope.resize = this.setScale;
conductor.on('timeSystem', this.changeTimeSystem);
//On conductor bounds changes, redraw ticks
conductor.on('bounds', this.setScale);
scope.$on("$destroy", this.destroy);
if (conductor.timeSystem() !== undefined) {
this.changeTimeSystem(conductor.timeSystem());
this.setScale();
}
};
return function (conductor, formatService) {
return new MCTConductorAxis(conductor, formatService);
template: '<div class="l-axis-holder" ' +
' mct-drag-down="axis.panStart()"' +
' mct-drag-up="axis.panStop(delta)"' +
' mct-drag="axis.pan(delta)"' +
' mct-resize="axis.resize()"></div>'
};
}
);
return MctConductorAxis;
});

View File

@ -26,7 +26,13 @@ define(
],
function (TimeConductorValidation) {
function TimeConductorController($scope, $window, timeConductor, conductorViewService, timeSystems) {
/**
* Controller for the Time Conductor UI element. The Time Conductor includes form fields for specifying time
* bounds and relative time deltas for queries, as well as controls for selection mode, time systems, and zooming.
* @memberof platform.features.conductor
* @constructor
*/
function TimeConductorController($scope, $window, openmct, conductorViewService, timeSystems, formatService) {
var self = this;
@ -40,9 +46,10 @@ define(
this.$scope = $scope;
this.$window = $window;
this.conductorViewService = conductorViewService;
this.conductor = timeConductor;
this.conductor = openmct.conductor;
this.modes = conductorViewService.availableModes();
this.validation = new TimeConductorValidation(this.conductor);
this.formatService = formatService;
// Construct the provided time system definitions
this.timeSystems = timeSystems.map(function (timeSystemConstructor) {
@ -52,7 +59,7 @@ define(
//Set the initial state of the view based on current time conductor
this.initializeScope();
this.conductor.on('bounds', this.setFormFromBounds);
this.conductor.on('bounds', this.changeBounds);
this.conductor.on('timeSystem', this.changeTimeSystem);
// If no mode selected, select fixed as the default
@ -71,8 +78,9 @@ define(
//If conductor has a time system selected already, populate the
//form from it
this.$scope.timeSystemModel = {};
if (this.conductor.timeSystem()) {
this.setFormFromTimeSystem(this.conductor.timeSystem());
var timeSystem = this.conductor.timeSystem();
if (timeSystem) {
this.setFormFromTimeSystem(timeSystem);
}
//Represents the various modes, and the currently selected mode
@ -98,34 +106,80 @@ define(
// Watch scope for selection of mode or time system by user
this.$scope.$watch('modeModel.selectedKey', this.setMode);
this.conductorViewService.on('pan', this.onPan);
this.conductorViewService.on('pan-stop', this.onPanStop);
this.$scope.$on('$destroy', this.destroy);
};
/**
* @private
*/
TimeConductorController.prototype.destroy = function () {
this.conductor.off('bounds', this.setFormFromBounds);
this.conductor.off('bounds', this.changeBounds);
this.conductor.off('timeSystem', this.changeTimeSystem);
this.conductorViewService.off('pan', this.onPan);
this.conductorViewService.off('pan-stop', this.onPanStop);
};
/**
* When the conductor bounds change, set the bounds in the form.
* @private
* @param {TimeConductorBounds} bounds
*/
TimeConductorController.prototype.changeBounds = function (bounds) {
//If a zoom or pan is currently in progress, do not override form values.
if (!this.zooming && !this.panning) {
this.setFormFromBounds(bounds);
}
};
/**
* Does the currently selected time system support zooming? To
* support zooming a time system must, at a minimum, define some
* values for maximum and minimum zoom levels. Additionally
* TimeFormats, a related concept, may also support providing time
* unit feedback for the zoom level label, eg "seconds, minutes,
* hours, etc..."
* @returns {boolean}
*/
TimeConductorController.prototype.supportsZoom = function () {
var timeSystem = this.conductor.timeSystem();
return timeSystem &&
timeSystem.defaults() &&
timeSystem.defaults().zoom;
};
/**
* Called when the bounds change in the time conductor. Synchronizes
* the bounds values in the time conductor with those in the form
*
* @private
* @param {TimeConductorBounds}
*/
TimeConductorController.prototype.setFormFromBounds = function (bounds) {
this.$scope.boundsModel.start = bounds.start;
this.$scope.boundsModel.end = bounds.end;
if (!this.pendingUpdate) {
this.pendingUpdate = true;
this.$window.requestAnimationFrame(function () {
this.pendingUpdate = false;
this.$scope.$digest();
}.bind(this));
if (!this.zooming && !this.panning) {
this.$scope.boundsModel.start = bounds.start;
this.$scope.boundsModel.end = bounds.end;
if (this.supportsZoom()) {
this.currentZoom = this.toSliderValue(bounds.end - bounds.start);
this.toTimeUnits(bounds.end - bounds.start);
}
if (!this.pendingUpdate) {
this.pendingUpdate = true;
this.$window.requestAnimationFrame(function () {
this.pendingUpdate = false;
this.$scope.$digest();
}.bind(this));
}
}
};
/**
* @private
* On mode change, populate form based on time systems available
* from the selected mode.
* @param mode
*/
TimeConductorController.prototype.setFormFromMode = function (mode) {
this.$scope.modeModel.selectedKey = mode;
@ -138,6 +192,7 @@ define(
};
/**
* When the deltas change, update the values in the UI
* @private
*/
TimeConductorController.prototype.setFormFromDeltas = function (deltas) {
@ -146,14 +201,20 @@ define(
};
/**
* @private
* Initialize the form when time system changes.
* @param {TimeSystem} timeSystem
*/
TimeConductorController.prototype.setFormFromTimeSystem = function (timeSystem) {
this.$scope.timeSystemModel.selected = timeSystem;
this.$scope.timeSystemModel.format = timeSystem.formats()[0];
this.$scope.timeSystemModel.deltaFormat = timeSystem.deltaFormat();
};
var timeSystemModel = this.$scope.timeSystemModel;
timeSystemModel.selected = timeSystem;
timeSystemModel.format = timeSystem.formats()[0];
timeSystemModel.deltaFormat = timeSystem.deltaFormat();
if (this.supportsZoom()) {
timeSystemModel.minZoom = timeSystem.defaults().zoom.min;
timeSystemModel.maxZoom = timeSystem.defaults().zoom.max;
}
};
/**
* Called when form values are changed. Synchronizes the form with
@ -222,11 +283,11 @@ define(
* Sets the selected time system. Will populate form with the default
* bounds and deltas defined in the selected time system.
*
* @private
* @param newTimeSystem
*/
TimeConductorController.prototype.changeTimeSystem = function (newTimeSystem) {
if (newTimeSystem && (newTimeSystem !== this.$scope.timeSystemModel.selected)) {
this.setFormFromTimeSystem(newTimeSystem);
if (newTimeSystem.defaults()) {
var deltas = newTimeSystem.defaults().deltas || {start: 0, end: 0};
var bounds = newTimeSystem.defaults().bounds || {start: 0, end: 0};
@ -234,10 +295,96 @@ define(
this.setFormFromDeltas(deltas);
this.setFormFromBounds(bounds);
}
this.setFormFromTimeSystem(newTimeSystem);
}
};
/**
* Takes a time span and calculates a slider increment value, used
* to set the horizontal offset of the slider.
* @param {number} timeSpan a duration of time, in ms
* @returns {number} a value between 0.01 and 0.99, in increments of .01
*/
TimeConductorController.prototype.toSliderValue = function (timeSpan) {
var timeSystem = this.conductor.timeSystem();
if (timeSystem) {
var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
var perc = timeSpan / (zoomDefaults.min - zoomDefaults.max);
return 1 - Math.pow(perc, 1 / 4);
}
};
/**
* Given a time span, set a label for the units of time that it,
* roughly, represents. Leverages
* @param {TimeSpan} timeSpan
*/
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
if (this.conductor.timeSystem()) {
var timeFormat = this.formatService.getFormat(this.conductor.timeSystem().formats()[0]);
this.$scope.timeUnits = timeFormat.timeUnits && timeFormat.timeUnits(timeSpan);
}
};
/**
* Zooming occurs when the user manipulates the zoom slider.
* Zooming updates the scale and bounds fields immediately, but does
* not trigger a bounds change to other views until the mouse button
* is released.
* @param bounds
*/
TimeConductorController.prototype.onZoom = function (sliderValue) {
var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
var timeSpan = Math.pow((1 - sliderValue), 4) * (zoomDefaults.min - zoomDefaults.max);
var zoom = this.conductorViewService.zoom(timeSpan);
this.$scope.boundsModel.start = zoom.bounds.start;
this.$scope.boundsModel.end = zoom.bounds.end;
this.toTimeUnits(zoom.bounds.end - zoom.bounds.start);
if (zoom.deltas) {
this.setFormFromDeltas(zoom.deltas);
}
};
/**
* Fired when user has released the zoom slider
* @event platform.features.conductor.TimeConductorController~zoomStop
*/
/**
* Invoked when zoom slider is released by user. Will update the time conductor with the new bounds, triggering
* a global bounds change event.
* @fires platform.features.conductor.TimeConductorController~zoomStop
*/
TimeConductorController.prototype.onZoomStop = function () {
this.updateBoundsFromForm(this.$scope.boundsModel);
this.updateDeltasFromForm(this.$scope.boundsModel);
this.zooming = false;
this.conductorViewService.emit('zoom-stop');
};
/**
* Panning occurs when the user grabs the conductor scale and drags
* it left or right to slide the window of time represented by the
* conductor. Panning updates the scale and bounds fields
* immediately, but does not trigger a bounds change to other views
* until the mouse button is released.
* @param {TimeConductorBounds} bounds
*/
TimeConductorController.prototype.onPan = function (bounds) {
this.panning = true;
this.$scope.boundsModel.start = bounds.start;
this.$scope.boundsModel.end = bounds.end;
};
/**
* Called when the user releases the mouse button after panning.
*/
TimeConductorController.prototype.onPanStop = function () {
this.panning = false;
};
return TimeConductorController;
}
);

View File

@ -28,6 +28,8 @@ define(['./TimeConductorController'], function (TimeConductorController) {
var mockConductorViewService;
var mockTimeSystems;
var controller;
var mockFormatService;
var mockFormat;
beforeEach(function () {
mockScope = jasmine.createSpyObj("$scope", [
@ -52,36 +54,33 @@ define(['./TimeConductorController'], function (TimeConductorController) {
"availableModes",
"mode",
"availableTimeSystems",
"deltas"
"deltas",
"deltas",
"on",
"off"
]
);
mockConductorViewService.availableModes.andReturn([]);
mockConductorViewService.availableTimeSystems.andReturn([]);
mockFormatService = jasmine.createSpyObj('formatService', [
'getFormat'
]);
mockFormat = jasmine.createSpyObj('format', [
'format'
]);
mockFormatService.getFormat.andReturn(mockFormat);
mockTimeSystems = [];
});
function getListener(name) {
return mockTimeConductor.on.calls.filter(function (call) {
return call.args[0] === name;
function getListener(target, event) {
return target.calls.filter(function (call) {
return call.args[0] === event;
})[0].args[1];
}
describe("", function () {
beforeEach(function () {
controller = new TimeConductorController(
mockScope,
mockWindow,
mockTimeConductor,
mockConductorViewService,
mockTimeSystems
);
});
});
describe("when time conductor state changes", function () {
var mockFormat;
var mockDeltaFormat;
var defaultBounds;
var defaultDeltas;
@ -119,17 +118,18 @@ define(['./TimeConductorController'], function (TimeConductorController) {
controller = new TimeConductorController(
mockScope,
mockWindow,
mockTimeConductor,
{conductor: mockTimeConductor},
mockConductorViewService,
mockTimeSystems
mockTimeSystems,
mockFormatService
);
tsListener = getListener("timeSystem");
tsListener = getListener(mockTimeConductor.on, "timeSystem");
});
it("listens for changes to conductor state", function () {
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.setFormFromBounds);
expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
});
it("deregisters conductor listens when scope is destroyed", function () {
@ -137,7 +137,7 @@ define(['./TimeConductorController'], function (TimeConductorController) {
controller.destroy();
expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.setFormFromBounds);
expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
});
it("when time system changes, sets time system on scope", function () {
@ -151,7 +151,11 @@ define(['./TimeConductorController'], function (TimeConductorController) {
});
it("when time system changes, sets defaults on scope", function () {
expect(tsListener).toBeDefined();
mockDefaults.zoom = {
min: 100,
max: 10
};
mockTimeConductor.timeSystem.andReturn(timeSystem);
tsListener(timeSystem);
expect(mockScope.boundsModel.start).toEqual(defaultBounds.start);
@ -159,6 +163,32 @@ define(['./TimeConductorController'], function (TimeConductorController) {
expect(mockScope.boundsModel.startDelta).toEqual(defaultDeltas.start);
expect(mockScope.boundsModel.endDelta).toEqual(defaultDeltas.end);
expect(mockScope.timeSystemModel.minZoom).toBe(mockDefaults.zoom.min);
expect(mockScope.timeSystemModel.maxZoom).toBe(mockDefaults.zoom.max);
});
it("when bounds change, sets the correct zoom slider value", function () {
var bounds = {
start: 0,
end: 50
};
mockDefaults.zoom = {
min: 100,
max: 0
};
function exponentializer(rawValue) {
return 1 - Math.pow(rawValue, 1 / 4);
}
mockTimeConductor.timeSystem.andReturn(timeSystem);
//Set zoom defaults
tsListener(timeSystem);
controller.changeBounds(bounds);
expect(controller.currentZoom).toEqual(exponentializer(0.5));
});
it("when bounds change, sets them on scope", function () {
@ -167,7 +197,7 @@ define(['./TimeConductorController'], function (TimeConductorController) {
end: 2
};
var boundsListener = getListener("bounds");
var boundsListener = getListener(mockTimeConductor.on, "bounds");
expect(boundsListener).toBeDefined();
boundsListener(bounds);
@ -225,9 +255,10 @@ define(['./TimeConductorController'], function (TimeConductorController) {
controller = new TimeConductorController(
mockScope,
mockWindow,
mockTimeConductor,
{conductor: mockTimeConductor},
mockConductorViewService,
mockTimeSystemConstructors
mockTimeSystemConstructors,
mockFormatService
);
mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
@ -240,9 +271,10 @@ define(['./TimeConductorController'], function (TimeConductorController) {
controller = new TimeConductorController(
mockScope,
mockWindow,
mockTimeConductor,
{conductor: mockTimeConductor},
mockConductorViewService,
mockTimeSystemConstructors
mockTimeSystemConstructors,
mockFormatService
);
mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
@ -264,9 +296,10 @@ define(['./TimeConductorController'], function (TimeConductorController) {
controller = new TimeConductorController(
mockScope,
mockWindow,
mockTimeConductor,
{conductor: mockTimeConductor},
mockConductorViewService,
mockTimeSystemConstructors
mockTimeSystemConstructors,
mockFormatService
);
controller.updateBoundsFromForm(formModel);
@ -286,9 +319,10 @@ define(['./TimeConductorController'], function (TimeConductorController) {
controller = new TimeConductorController(
mockScope,
mockWindow,
mockTimeConductor,
{conductor: mockTimeConductor},
mockConductorViewService,
mockTimeSystemConstructors
mockTimeSystemConstructors,
mockFormatService
);
controller.updateDeltasFromForm(formModel);
@ -321,14 +355,33 @@ define(['./TimeConductorController'], function (TimeConductorController) {
controller = new TimeConductorController(
mockScope,
mockWindow,
mockTimeConductor,
{conductor: mockTimeConductor},
mockConductorViewService,
mockTimeSystems
mockTimeSystems,
mockFormatService
);
controller.selectTimeSystemByKey('testTimeSystem');
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
});
it("updates form bounds during pan events", function () {
var testBounds = {
start: 10,
end: 20
};
expect(controller.$scope.boundsModel.start).not.toBe(testBounds.start);
expect(controller.$scope.boundsModel.end).not.toBe(testBounds.end);
expect(controller.conductorViewService.on).toHaveBeenCalledWith("pan",
controller.onPan);
getListener(controller.conductorViewService.on, "pan")(testBounds);
expect(controller.$scope.boundsModel.start).toBe(testBounds.start);
expect(controller.$scope.boundsModel.end).toBe(testBounds.end);
});
});
});

View File

@ -28,13 +28,14 @@ define(
* Supports mode-specific time conductor behavior.
*
* @constructor
* @memberof platform.features.conductor
* @param {TimeConductorMetadata} metadata
*/
function TimeConductorMode(metadata, conductor, timeSystems) {
this.conductor = conductor;
this.mdata = metadata;
this.dlts = undefined;
this.deltasVal = undefined;
this.source = undefined;
this.sourceUnlisten = undefined;
this.systems = timeSystems;
@ -141,6 +142,9 @@ define(
return this.source;
};
/**
* @private
*/
TimeConductorMode.prototype.destroy = function () {
this.conductor.off('timeSystem', this.changeTimeSystem);
@ -177,23 +181,66 @@ define(
*/
TimeConductorMode.prototype.deltas = function (deltas) {
if (arguments.length !== 0) {
var oldEnd = this.conductor.bounds().end;
if (this.dlts && this.dlts.end !== undefined) {
//Calculate the previous raw end value (without delta)
oldEnd = oldEnd - this.dlts.end;
var bounds = this.calculateBoundsFromDeltas(deltas);
this.deltasVal = deltas;
if (this.metadata().key !== 'fixed') {
this.conductor.bounds(bounds);
}
this.dlts = deltas;
var newBounds = {
start: oldEnd - this.dlts.start,
end: oldEnd + this.dlts.end
};
this.conductor.bounds(newBounds);
}
return this.dlts;
return this.deltasVal;
};
/**
* @param deltas
* @returns {TimeConductorBounds}
*/
TimeConductorMode.prototype.calculateBoundsFromDeltas = function (deltas) {
var oldEnd = this.conductor.bounds().end;
if (this.deltasVal && this.deltasVal.end !== undefined) {
//Calculate the previous raw end value (without delta)
oldEnd = oldEnd - this.deltasVal.end;
}
var bounds = {
start: oldEnd - deltas.start,
end: oldEnd + deltas.end
};
return bounds;
};
/**
* @typedef {Object} ZoomLevel
* @property {TimeConductorBounds} bounds The calculated bounds based on the zoom level
* @property {TimeConductorDeltas} deltas The calculated deltas based on the zoom level
*/
/**
* Calculates bounds and deltas based on provided timeSpan. Collectively
* the bounds and deltas will constitute the new zoom level.
* @param {number} timeSpan time duration in ms.
* @return {ZoomLevel} The new zoom bounds and delta calculated for the provided time span
*/
TimeConductorMode.prototype.calculateZoom = function (timeSpan) {
var zoom = {};
// If a tick source is defined, then the concept of 'now' is
// important. Calculate zoom based on 'now'.
if (this.tickSource()) {
zoom.deltas = {
start: timeSpan,
end: this.deltasVal.end
};
zoom.bounds = this.calculateBoundsFromDeltas(zoom.deltas);
// Calculate bounds based on deltas;
} else {
var bounds = this.conductor.bounds();
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
bounds.start = center - timeSpan / 2;
bounds.end = center + timeSpan / 2;
zoom.bounds = bounds;
}
return zoom;
};
return TimeConductorMode;

View File

@ -22,25 +22,30 @@
define(
[
'EventEmitter',
'./TimeConductorMode'
],
function (TimeConductorMode) {
function (EventEmitter, TimeConductorMode) {
/**
* A class representing the state of the time conductor view. This
* exposes details of the UI that are not represented on the
* TimeConductor API itself such as modes and deltas.
*
* @memberof platform.features.conductor
* @param conductor
* @param timeSystems
* @constructor
*/
function TimeConductorViewService(conductor, timeSystems) {
function TimeConductorViewService(openmct, timeSystems) {
EventEmitter.call(this);
this.systems = timeSystems.map(function (timeSystemConstructor) {
return timeSystemConstructor();
});
this.conductor = conductor;
this.conductor = openmct.conductor;
this.currentMode = undefined;
/**
@ -97,6 +102,8 @@ define(
}
}
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
/**
* Getter/Setter for the Time Conductor Mode. Modes determine the
* behavior of the time conductor, especially with regards to the
@ -144,7 +151,7 @@ define(
};
/**
* @typedef {object} Delta
* @typedef {object} TimeConductorDeltas
* @property {number} start Used to set the start bound of the
* TimeConductor on tick. A positive value that will be subtracted
* from the value provided by a tick source to determine the start
@ -171,7 +178,7 @@ define(
* tick
* - end: A time in ms after the timestamp of the last data received
* which will be used to determine the 'end' bound on tick
* @returns {Delta} current value of the deltas
* @returns {TimeConductorDeltas} current value of the deltas
*/
TimeConductorViewService.prototype.deltas = function () {
//Deltas stored on mode. Use .apply to preserve arguments
@ -197,6 +204,26 @@ define(
return this.currentMode.availableTimeSystems();
};
/**
* An event to indicate that zooming is taking place
* @event platform.features.conductor.TimeConductorViewService~zoom
* @property {ZoomLevel} zoom the new zoom level.
*/
/**
* Zoom to given time span. Will fire a zoom event with new zoom
* bounds. Zoom bounds emitted in this way are considered ephemeral
* and should be overridden by any time conductor bounds events. Does
* not set bounds globally.
* @param {number} zoom A time duration in ms
* @fires platform.features.conductor.TimeConductorViewService~zoom
* @see module:openmct.TimeConductor#bounds
*/
TimeConductorViewService.prototype.zoom = function (timeSpan) {
var zoom = this.currentMode.calculateZoom(timeSpan);
this.emit("zoom", zoom);
return zoom;
};
return TimeConductorViewService;
}
);

View File

@ -87,7 +87,7 @@ define(['./TimeConductorViewService'], function (TimeConductorViewService) {
it("At a minimum supports fixed mode", function () {
var mockTimeSystems = [mockConstructor(basicTimeSystem)];
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
var availableModes = viewService.availableModes();
expect(availableModes.fixed).toBeDefined();
@ -102,7 +102,7 @@ define(['./TimeConductorViewService'], function (TimeConductorViewService) {
};
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
var availableModes = viewService.availableModes();
expect(availableModes.realtime).toBeDefined();
@ -117,7 +117,7 @@ define(['./TimeConductorViewService'], function (TimeConductorViewService) {
};
tickingTimeSystem.tickSources.andReturn([mockLADTickSource]);
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
var availableModes = viewService.availableModes();
expect(availableModes.lad).toBeDefined();
@ -132,7 +132,7 @@ define(['./TimeConductorViewService'], function (TimeConductorViewService) {
"destroy"
]);
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
viewService.currentMode = oldMode;
viewService.mode('fixed');
expect(oldMode.destroy).toHaveBeenCalled();
@ -149,7 +149,7 @@ define(['./TimeConductorViewService'], function (TimeConductorViewService) {
};
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
//Set time system to one known to support realtime mode
mockTimeConductor.timeSystem.andReturn(tickingTimeSystem);
@ -169,7 +169,7 @@ define(['./TimeConductorViewService'], function (TimeConductorViewService) {
};
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
//Set time system to one known to not support realtime mode
mockTimeConductor.timeSystem.andReturn(basicTimeSystem);

View File

@ -0,0 +1,109 @@
/*****************************************************************************
* 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 () {
/**
* Controller for the Time of Interest element used in various views to display the TOI. Responsible for setting
* the text label for the current TOI, and for toggling the (un)pinned state which determines whether the TOI
* indicator is visible.
* @constructor
*/
function TimeOfInterestController($scope, openmct, formatService) {
this.conductor = openmct.conductor;
this.formatService = formatService;
this.format = undefined;
this.toiText = undefined;
this.$scope = $scope;
//Bind all class functions to 'this'
Object.keys(TimeOfInterestController.prototype).filter(function (key) {
return typeof TimeOfInterestController.prototype[key] === 'function';
}).forEach(function (key) {
this[key] = TimeOfInterestController.prototype[key].bind(this);
}.bind(this));
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
this.conductor.on('timeSystem', this.changeTimeSystem);
if (this.conductor.timeSystem()) {
this.changeTimeSystem(this.conductor.timeSystem());
var toi = this.conductor.timeOfInterest();
if (toi) {
this.changeTimeOfInterest(toi);
}
}
$scope.$on('$destroy', this.destroy);
}
/**
* Called when the time of interest changes on the conductor. Will pin (display) the TOI indicator, and set the
* text using the default formatter of the currently active Time System.
* @private
* @param {integer} toi Current time of interest in ms
*/
TimeOfInterestController.prototype.changeTimeOfInterest = function (toi) {
if (toi !== undefined) {
this.$scope.pinned = true;
this.toiText = this.format.format(toi);
} else {
this.$scope.pinned = false;
}
};
/**
* When time system is changed, update the formatter used to
* display the current TOI label
*/
TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) {
this.format = this.formatService.getFormat(timeSystem.formats()[0]);
};
/**
* @private
*/
TimeOfInterestController.prototype.destroy = function () {
this.conductor.off('timeOfInterest', this.changeTimeOfInterest);
this.conductor.off('timeSystem', this.changeTimeSystem);
};
/**
* Will unpin (hide) the TOI indicator. Has the effect of setting the time of interest to `undefined` on the
* Time Conductor
*/
TimeOfInterestController.prototype.dismiss = function () {
this.conductor.timeOfInterest(undefined);
};
/**
* Sends out a time of interest event with the effect of resetting
* the TOI displayed in views.
*/
TimeOfInterestController.prototype.resync = function () {
this.conductor.timeOfInterest(this.conductor.timeOfInterest());
};
return TimeOfInterestController;
}
);

View File

@ -0,0 +1,115 @@
/*****************************************************************************
* 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(['./TimeOfInterestController'], function (TimeOfInterestController) {
describe("The time of interest controller", function () {
var controller;
var mockScope;
var mockConductor;
var mockFormatService;
var mockTimeSystem;
var mockFormat;
beforeEach(function () {
mockConductor = jasmine.createSpyObj("conductor", [
"on",
"timeSystem"
]);
mockScope = jasmine.createSpyObj("scope", [
"$on"
]);
mockFormat = jasmine.createSpyObj("format", [
"format"
]);
mockFormatService = jasmine.createSpyObj("formatService", [
"getFormat"
]);
mockFormatService.getFormat.andReturn(mockFormat);
mockTimeSystem = {
formats: function () {
return ["mockFormat"];
}
};
controller = new TimeOfInterestController(mockScope, {conductor: mockConductor}, mockFormatService);
});
function getCallback(target, event) {
return target.calls.filter(function (call) {
return call.args[0] === event;
})[0].args[1];
}
it("Listens for changes to TOI", function () {
expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", controller.changeTimeOfInterest);
});
it("updates format when time system changes", function () {
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
getCallback(mockConductor.on, "timeSystem")(mockTimeSystem);
expect(controller.format).toBe(mockFormat);
});
describe("When TOI changes", function () {
var toi;
var toiCallback;
var formattedTOI;
beforeEach(function () {
var timeSystemCallback = getCallback(mockConductor.on, "timeSystem");
toi = 1;
mockConductor.timeSystem.andReturn(mockTimeSystem);
//Set time system
timeSystemCallback(mockTimeSystem);
toiCallback = getCallback(mockConductor.on, "timeOfInterest");
formattedTOI = "formatted TOI";
mockFormatService.getFormat.andReturn("mockFormat");
mockFormat.format.andReturn(formattedTOI);
});
it("Uses the time system formatter to produce TOI text", function () {
toiCallback = getCallback(mockConductor.on, "timeOfInterest");
//Set TOI
toiCallback(toi);
expect(mockFormat.format).toHaveBeenCalled();
});
it("Sets the time of interest text", function () {
//Set TOI
toiCallback(toi);
expect(controller.toiText).toBe(formattedTOI);
});
it("Pins the time of interest", function () {
//Set TOI
toiCallback(toi);
expect(mockScope.pinned).toBe(true);
});
});
});
});

View File

@ -64,13 +64,17 @@ define([
return this.sources;
};
UTCTimeSystem.prototype.defaults = function (key) {
UTCTimeSystem.prototype.defaults = function () {
var now = Math.ceil(Date.now() / 1000) * 1000;
var ONE_MINUTE = 60 * 1 * 1000;
var FIFTY_YEARS = 50 * 365 * 24 * 60 * 60 * 1000;
return {
key: 'utc-default',
name: 'UTC time system defaults',
deltas: {start: FIFTEEN_MINUTES, end: 0},
bounds: {start: now - FIFTEEN_MINUTES, end: now}
bounds: {start: now - FIFTEEN_MINUTES, end: now},
zoom: {min: FIFTY_YEARS, max: ONE_MINUTE}
};
};

View File

@ -164,7 +164,7 @@ $ueTimeConductorH: (33px, 18px, 20px);
margin-left: 0;
}
.l-time-range-tick-label {
@include webkitProp(transform, translateX(-50%));
@include transform(translateX(-50%));
color: $colorPlotLabelFg;
display: inline-block;
font-size: 0.7rem;

View File

@ -77,7 +77,8 @@ define([
"telemetryFormatter",
"telemetryHandler",
"throttle",
"PLOT_FIXED_DURATION"
"PLOT_FIXED_DURATION",
"openmct"
]
},
{

View File

@ -40,21 +40,16 @@
ng-style="{ height: 100 / plot.getSubPlots().length + '%'}"
ng-repeat="subplot in plot.getSubPlots()">
<div class="gl-plot-legend">
<!-- ng-class is temporarily hard-coded in next element -->
<span
class='plot-legend-item'
ng-repeat="telemetryObject in subplot.getTelemetryObjects()"
ng-class="plot.getLegendClass(telemetryObject)">
<span class='plot-color-swatch'
ng-style="{ 'background-color': plot.getColor($index) }">
<span class='plot-legend-item'
ng-repeat="telemetryObject in subplot.getTelemetryObjects()"
ng-class="plot.getLegendClass(telemetryObject)">
<span class='plot-color-swatch'
ng-style="{ 'background-color': plot.getColor($index) }">
</span>
<span class='title-label'>{{telemetryObject.getModel().name}}</span>
</span>
<span class='title-label'>{{telemetryObject.getModel().name}}</span>
</span>
</div>
<div class="gl-plot-coords"
ng-if="subplot.isHovering() && subplot.getHoverCoordinates()">
{{subplot.getHoverCoordinates()}}
</div>
<div class="gl-plot-axis-area gl-plot-y">
<div class="gl-plot-label gl-plot-y-label">
{{axes[1].active.name}}
@ -74,81 +69,96 @@
</div>
</div>
</div>
<div class="gl-plot-display-area"
ng-mouseenter="subplot.isHovering(true);"
ng-mouseleave="subplot.isHovering(false)"
ng-class="{ loading: plot.isRequestPending() }">
<!-- Out-of-bounds data indicators -->
<!-- ng-show is temporarily hard-coded in next element -->
<div ng-show="false" class="l-oob-data l-oob-data-up"></div>
<div ng-show="false" class="l-oob-data l-oob-data-dwn"></div>
<div class="gl-plot-hash hash-v"
ng-repeat="tick in subplot.getDomainTicks()"
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%', height: '100%' }"
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)">
<div class="gl-plot-wrapper-display-area-and-x-axis">
<mct-include key="'time-of-interest'"
class="l-toi-holder show-val"
ng-if="toiPerc"
ng-class="{ 'pinned': toiPinned, 'val-to-left': toiPerc > 80 }"
ng-style="{'left': toiPerc + '%'}"></mct-include>
<div class="gl-plot-coords"
ng-if="subplot.isHovering() && subplot.getHoverCoordinates()">
{{subplot.getHoverCoordinates()}}
</div>
<div class="gl-plot-hash hash-h"
ng-repeat="tick in subplot.getRangeTicks()"
ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%', width: '100%' }"
ng-show="$index > 0 && $index < (subplot.getRangeTicks().length - 1)">
</div>
<mct-chart draw="subplot.getDrawingObject()"
ng-if="subplot.getTelemetryObjects().length > 0"
ng-mousemove="subplot.hover($event)"
mct-drag="subplot.continueDrag($event)"
mct-drag-down="subplot.startDrag($event)"
mct-drag-up="subplot.endDrag($event); plot.update()">
</mct-chart>
<!-- TODO: Move into correct position; make part of group; infer from set of actions -->
<div class="l-local-controls gl-plot-local-controls t-plot-display-controls"
ng-if="$first">
<a class="s-button icon-arrow-left"
ng-click="plot.stepBackPanZoom()"
ng-show="plot.isZoomed()"
title="Restore previous pan/zoom">
</a>
<a class="s-button icon-arrows-out"
ng-click="plot.unzoom()"
ng-show="plot.isZoomed()"
title="Reset pan/zoom">
</a>
<div class="menu-element s-menu-button menus-to-left {{plot.getMode().cssclass}}"
ng-if="plot.getModeOptions().length > 1"
ng-controller="ClickAwayController as toggle">
<span class="l-click-area" ng-click="toggle.toggle()"></span>
<span>{{plot.getMode().name}}</span>
<div class="menu" ng-show="toggle.isActive()">
<ul>
<li ng-repeat="option in plot.getModeOptions()"
ng-click="plot.setMode(option); toggle.setState(false)"
class="{{option.cssclass}}">
{{option.name}}
</li>
</ul>
<div class="gl-plot-display-area"
ng-mouseenter="subplot.isHovering(true);"
ng-mouseleave="subplot.isHovering(false)"
ng-class="{ loading: plot.isRequestPending() }">
<!-- Out-of-bounds data indicators -->
<!-- ng-show is temporarily hard-coded in next element -->
<div ng-show="false" class="l-oob-data l-oob-data-up"></div>
<div ng-show="false" class="l-oob-data l-oob-data-dwn"></div>
<div class="gl-plot-hash hash-v"
ng-repeat="tick in subplot.getDomainTicks()"
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%', height: '100%' }"
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)">
</div>
<div class="gl-plot-hash hash-h"
ng-repeat="tick in subplot.getRangeTicks()"
ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%', width: '100%' }"
ng-show="$index > 0 && $index < (subplot.getRangeTicks().length - 1)">
</div>
<mct-chart draw="subplot.getDrawingObject()"
ng-if="subplot.getTelemetryObjects().length > 0"
ng-mousemove="subplot.hover($event)"
mct-drag="subplot.continueDrag($event)"
mct-drag-down="subplot.startDrag($event)"
mct-drag-up="subplot.endDrag($event); plot.update()">
</mct-chart>
<!-- TODO: Move into correct position; make part of group; infer from set of actions -->
<div class="l-local-controls gl-plot-local-controls t-plot-display-controls"
ng-if="$first">
<a class="s-button icon-arrow-left"
ng-click="plot.stepBackPanZoom()"
ng-show="plot.isZoomed()"
title="Restore previous pan/zoom">
</a>
<a class="s-button icon-arrows-out"
ng-click="plot.unzoom()"
ng-show="plot.isZoomed()"
title="Reset pan/zoom">
</a>
<div class="menu-element s-menu-button menus-to-left {{plot.getMode().cssclass}}"
ng-if="plot.getModeOptions().length > 1"
ng-controller="ClickAwayController as toggle">
<span class="l-click-area" ng-click="toggle.toggle()"></span>
<span>{{plot.getMode().name}}</span>
<div class="menu" ng-show="toggle.isActive()">
<ul>
<li ng-repeat="option in plot.getModeOptions()"
ng-click="plot.setMode(option); toggle.setState(false)"
class="{{option.cssclass}}">
{{option.name}}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div ng-if="$last" class="gl-plot-axis-area gl-plot-x">
<div ng-repeat="tick in subplot.getDomainTicks()"
class="gl-plot-tick gl-plot-x-tick-label"
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)"
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%' }">
{{tick.label | reverse}}
</div>
<div class="gl-plot-label gl-plot-x-label">
{{axes[0].active.name}}
</div>
<div class="gl-plot-x-options gl-plot-local-controls"
ng-if="axes[0].options.length > 1">
<div class='form-control shell select'>
<select class="form-control input shell"
ng-model="axes[0].active"
ng-options="option.name for option in axes[0].options">
</select>
<div ng-if="$last" class="gl-plot-axis-area gl-plot-x">
<div ng-repeat="tick in subplot.getDomainTicks()"
class="gl-plot-tick gl-plot-x-tick-label"
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)"
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%' }">
{{tick.label | reverse}}
</div>
<div class="gl-plot-label gl-plot-x-label">
{{axes[0].active.name}}
</div>
<div class="gl-plot-x-options gl-plot-local-controls"
ng-if="axes[0].options.length > 1">
<div class='form-control shell select'>
<select class="form-control input shell"
ng-model="axes[0].active"
ng-options="option.name for option in axes[0].options">
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -68,7 +68,8 @@ define(
telemetryFormatter,
telemetryHandler,
throttle,
PLOT_FIXED_DURATION
PLOT_FIXED_DURATION,
openmct
) {
var self = this,
plotTelemetryFormatter =
@ -81,6 +82,7 @@ define(
lastRange,
lastDomain,
handle;
var conductor = openmct.conductor;
// Populate the scope with axis information (specifically, options
// available for each axis.)
@ -181,6 +183,18 @@ define(
}
}
function changeTimeOfInterest(timeOfInterest) {
if (timeOfInterest !== undefined) {
var bounds = conductor.bounds();
var range = bounds.end - bounds.start;
$scope.toiPerc = ((timeOfInterest - bounds.start) / range) * 100;
$scope.toiPinned = true;
} else {
$scope.toiPerc = undefined;
$scope.toiPinned = false;
}
}
// Create a new subscription; telemetrySubscriber gets
// to do the meaningful work here.
function subscribe(domainObject) {
@ -193,6 +207,9 @@ define(
true // Lossless
);
replot();
changeTimeOfInterest(conductor.timeOfInterest());
conductor.on("timeOfInterest", changeTimeOfInterest);
}
// Release the current subscription (called when scope is destroyed)
@ -200,6 +217,7 @@ define(
if (handle) {
handle.unsubscribe();
handle = undefined;
conductor.off("timeOfInterest", changeTimeOfInterest);
}
}
@ -242,6 +260,7 @@ define(
requery();
}
self.setUnsynchedStatus($scope.domainObject, follow && self.isZoomed());
changeTimeOfInterest(conductor.timeOfInterest());
}
this.modeOptions = new PlotModeOptions([], subPlotFactory);

View File

@ -40,7 +40,8 @@ define(
mockDomainObject,
mockSeries,
mockStatusCapability,
controller;
controller,
mockConductor;
function bind(method, thisObj) {
return function () {
@ -120,13 +121,23 @@ define(
mockHandle.getRangeValue.andReturn(42);
mockScope.domainObject = mockDomainObject;
mockConductor = jasmine.createSpyObj('conductor', [
'on',
'off',
'bounds',
'timeSystem',
'timeOfInterest'
]);
controller = new PlotController(
mockScope,
mockElement,
mockExportImageService,
mockFormatter,
mockHandler,
mockThrottle
mockThrottle,
undefined,
{conductor: mockConductor}
);
});

View File

@ -115,12 +115,12 @@ define([
{
"key": "HistoricalTableController",
"implementation": HistoricalTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout"]
"depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout", "openmct"]
},
{
"key": "RealtimeTableController",
"implementation": RealtimeTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
"depends": ["$scope", "telemetryHandler", "telemetryFormatter", "openmct"]
},
{
"key": "TableOptionsController",

View File

@ -1,9 +1,12 @@
<div ng-controller="HistoricalTableController" ng-class="{'loading': loading}">
<div ng-controller="HistoricalTableController as tableController"
ng-class="{'loading': loading}">
<mct-table
headers="headers"
time-columns="tableController.timeColumns"
rows="rows"
enableFilter="true"
enableSort="true"
sort-column="defaultSort"
class="tabular-holder has-control-bar">
</mct-table>
</div>

View File

@ -48,12 +48,19 @@
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="visibleRow in visibleRows track by visibleRow.rowIndex"
ng-style="{
top: visibleRow.offsetY + 'px',
}">
<tr ng-repeat-start="visibleRow in visibleRows track by visibleRow.rowIndex"
ng-if="visibleRow.rowIndex === toiRowIndex"
ng-style="{ top: visibleRow.offsetY + 'px' }"
class="l-toi-tablerow">
<td colspan="999">
<mct-include key="'time-of-interest'"
class="l-toi-holder pinned"></mct-include>
</td>
</tr>
<tr ng-repeat-end
ng-style="{ top: visibleRow.offsetY + 'px' }"
ng-click="table.onRowClick($event, visibleRow.rowIndex) ">
<td ng-repeat="header in displayHeaders"
ng-style=" {
width: columnWidths[$index] + 'px',

View File

@ -1,10 +1,12 @@
<div ng-controller="RealtimeTableController">
<div ng-controller="RealtimeTableController as tableController">
<mct-table
headers="headers"
rows="rows"
time-columns="tableController.timeColumns"
enableFilter="true"
enableSort="true"
class="tabular-holder has-control-bar"
sort-column="defaultSort"
auto-scroll="true">
</mct-table>
</div>

View File

@ -36,7 +36,7 @@ define(
* @param telemetryFormatter
* @constructor
*/
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter, $timeout) {
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter, $timeout, openmct) {
var self = this;
this.$timeout = $timeout;
@ -49,7 +49,7 @@ define(
}
});
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
TableController.call(this, $scope, telemetryHandler, telemetryFormatter, openmct);
}
HistoricalTableController.prototype = Object.create(TableController.prototype);

View File

@ -12,7 +12,7 @@ define(
* @param element
* @constructor
*/
function MCTTableController($scope, $timeout, element, exportService) {
function MCTTableController($scope, $timeout, element, exportService, formatService, openmct) {
var self = this;
this.$scope = $scope;
@ -24,6 +24,16 @@ define(
this.resultsHeader = this.element.find('.mct-table>thead').first();
this.sizingTableBody = this.element.find('.sizing-table>tbody').first();
this.$scope.sizingRow = {};
this.conductor = openmct.conductor;
this.toiFormatter = undefined;
this.formatService = formatService;
//Bind all class functions to 'this'
Object.keys(MCTTableController.prototype).filter(function (key) {
return typeof MCTTableController.prototype[key] === 'function';
}).forEach(function (key) {
this[key] = MCTTableController.prototype[key].bind(this);
}.bind(this));
this.scrollable.on('scroll', this.onScroll.bind(this));
@ -42,6 +52,9 @@ define(
scope.sortColumn = undefined;
scope.sortDirection = undefined;
}
if (scope.sortColumn !== undefined) {
scope.sortDirection = "asc";
}
}
setDefaults($scope);
@ -68,8 +81,12 @@ define(
} else if ($scope.sortDirection === 'desc') {
$scope.sortColumn = undefined;
$scope.sortDirection = undefined;
} else if ($scope.sortColumn !== undefined &&
$scope.sortDirection === undefined) {
$scope.sortDirection = 'asc';
}
self.setRows($scope.rows);
self.setTimeOfInterest(self.conductor.timeOfInterest());
};
/*
@ -78,22 +95,60 @@ define(
$scope.$watchCollection('filters', function () {
self.setRows($scope.rows);
});
$scope.$watch('headers', this.setHeaders.bind(this));
$scope.$watch('rows', this.setRows.bind(this));
$scope.$watch('headers', this.setHeaders);
$scope.$watch('rows', this.setRows);
/*
* Listen for rows added individually (eg. for real-time tables)
*/
$scope.$on('add:row', this.addRow.bind(this));
$scope.$on('remove:row', this.removeRow.bind(this));
$scope.$on('add:row', this.addRow);
$scope.$on('remove:row', this.removeRow);
/**
* Populated from the default-sort attribute on MctTable
* directive tag.
*/
$scope.$watch('sortColumn', $scope.toggleSort);
/*
* Listen for resize events to trigger recalculation of table width
*/
$scope.resize = this.setElementSizes.bind(this);
$scope.resize = this.setElementSizes;
/**
* Scope variable that is populated from the 'time-columns'
* attribute on the MctTable tag. Indicates which columns, while
* sorted, can be used for indicated time of interest.
*/
$scope.$watch("timeColumns", function (timeColumns) {
if (timeColumns) {
this.destroyConductorListeners();
this.conductor.on('timeSystem', this.changeTimeSystem);
this.conductor.on('timeOfInterest', this.setTimeOfInterest);
this.conductor.on('bounds', this.changeBounds);
// If time system defined, set initially
if (this.conductor.timeSystem()) {
this.changeTimeSystem(this.conductor.timeSystem());
}
}
}.bind(this));
$scope.$on('$destroy', this.destroyConductorListeners);
}
MCTTableController.prototype.destroyConductorListeners = function () {
this.conductor.off('timeSystem', this.changeTimeSystem);
this.conductor.off('timeOfInterest', this.setTimeOfInterest);
this.conductor.off('bounds', this.changeBounds);
};
MCTTableController.prototype.changeTimeSystem = function () {
var format = this.conductor.timeSystem().formats()[0];
this.toiFormatter = this.formatService.getFormat(format);
};
/**
* If auto-scroll is enabled, this function will scroll to the
* bottom of the page
@ -163,19 +218,54 @@ define(
this.$scope.$digest();
};
/**
* Return first visible row, based on current scroll state.
* @private
*/
MCTTableController.prototype.firstVisible = function () {
var target = this.scrollable[0],
topScroll = target.scrollTop,
firstVisible;
if (topScroll < this.$scope.headerHeight) {
firstVisible = 0;
} else {
firstVisible = Math.floor(
(topScroll - this.$scope.headerHeight) /
this.$scope.rowHeight
);
}
return firstVisible;
};
/**
* Return last visible row, based on current scroll state.
* @private
*/
MCTTableController.prototype.lastVisible = function () {
var target = this.scrollable[0],
topScroll = target.scrollTop,
bottomScroll = topScroll + target.offsetHeight,
lastVisible;
lastVisible = Math.ceil(
(bottomScroll - this.$scope.headerHeight) /
this.$scope.rowHeight
);
return lastVisible;
};
/**
* Sets visible rows based on array
* content and current scroll state.
*/
MCTTableController.prototype.setVisibleRows = function () {
var self = this,
target = this.scrollable[0],
topScroll = target.scrollTop,
bottomScroll = topScroll + target.offsetHeight,
firstVisible,
lastVisible,
totalVisible,
numberOffscreen,
firstVisible,
lastVisible,
start,
end;
@ -184,21 +274,8 @@ define(
start = 0;
end = this.$scope.displayRows.length;
} else {
//rows has exceeded display maximum, so may be necessary to
// scroll
if (topScroll < this.$scope.headerHeight) {
firstVisible = 0;
} else {
firstVisible = Math.floor(
(topScroll - this.$scope.headerHeight) /
this.$scope.rowHeight
);
}
lastVisible = Math.ceil(
(bottomScroll - this.$scope.headerHeight) /
this.$scope.rowHeight
);
firstVisible = this.firstVisible();
lastVisible = this.lastVisible();
totalVisible = lastVisible - firstVisible;
numberOffscreen = this.maxDisplayRows - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
@ -294,37 +371,37 @@ define(
/**
* @private
*/
MCTTableController.prototype.insertSorted = function (array, element) {
var index = -1,
self = this,
sortKey = this.$scope.sortColumn;
MCTTableController.prototype.binarySearch = function (searchArray, searchElement, min, max) {
var sampleAt = Math.floor((max - min) / 2) + min;
function binarySearch(searchArray, searchElement, min, max) {
var sampleAt = Math.floor((max - min) / 2) + min;
if (max < min) {
return min; // Element is not in array, min gives direction
}
switch (self.sortComparator(searchElement[sortKey].text,
searchArray[sampleAt][sortKey].text)) {
case -1:
return binarySearch(searchArray, searchElement, min,
sampleAt - 1);
case 0 :
return sampleAt;
case 1 :
return binarySearch(searchArray, searchElement,
sampleAt + 1, max);
}
if (max < min) {
return min; // Element is not in array, min gives direction
}
switch (this.sortComparator(searchElement,
searchArray[sampleAt][this.$scope.sortColumn].text)) {
case -1:
return this.binarySearch(searchArray, searchElement, min,
sampleAt - 1);
case 0 :
return sampleAt;
case 1 :
return this.binarySearch(searchArray, searchElement,
sampleAt + 1, max);
}
};
/**
* @private
*/
MCTTableController.prototype.insertSorted = function (array, element) {
var index = -1;
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
//No sorting applied, push it on the end.
index = array.length;
} else {
//Sort is enabled, perform binary search to find insertion point
index = binarySearch(array, element, 0, array.length - 1);
index = this.binarySearch(array, element[this.$scope.sortColumn].text, 0, array.length - 1);
}
if (index === -1) {
array.unshift(element);
@ -485,7 +562,19 @@ define(
}
this.$scope.displayRows = this.filterAndSort(newRows || []);
this.resize(newRows).then(this.setVisibleRows.bind(this));
this.resize(newRows)
.then(this.setVisibleRows)
//Timeout following setVisibleRows to allow digest to
// perform DOM changes, otherwise scrollTo won't work.
.then(this.$timeout)
.then(function () {
//If TOI specified, scroll to it
var timeOfInterest = this.conductor.timeOfInterest();
if (timeOfInterest) {
this.setTimeOfInterest(timeOfInterest);
}
}.bind(this));
};
/**
@ -525,6 +614,70 @@ define(
return rowsToFilter.filter(matchRow.bind(null, filters));
};
/**
* @param displayRowIndex {number} The index in the displayed rows
* to scroll to.
*/
MCTTableController.prototype.scrollToRow = function (displayRowIndex) {
var visible = displayRowIndex > this.firstVisible() && displayRowIndex < this.lastVisible();
if (!visible) {
var scrollTop = displayRowIndex * this.$scope.rowHeight +
this.$scope.headerHeight -
(this.scrollable[0].offsetHeight / 2);
this.scrollable[0].scrollTop = scrollTop;
this.setVisibleRows();
}
};
/**
* Update rows with new data. If filtering is enabled, rows
* will be sorted before display.
*/
MCTTableController.prototype.setTimeOfInterest = function (newTOI) {
var isSortedByTime =
this.$scope.timeColumns &&
this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1;
this.$scope.toiRowIndex = -1;
if (newTOI && isSortedByTime) {
var formattedTOI = this.toiFormatter.format(newTOI);
var rowIndex = this.binarySearch(
this.$scope.displayRows,
formattedTOI,
0,
this.$scope.displayRows.length - 1);
if (rowIndex > 0 && rowIndex < this.$scope.displayRows.length) {
this.$scope.toiRowIndex = rowIndex;
this.scrollToRow(this.$scope.toiRowIndex);
}
}
};
/**
* On zoom, pan, etc. reset TOI
* @param bounds
*/
MCTTableController.prototype.changeBounds = function (bounds) {
this.setTimeOfInterest(this.conductor.timeOfInterest());
};
/**
* @private
*/
MCTTableController.prototype.onRowClick = function (event, rowIndex) {
if (this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1) {
var selectedTime = this.$scope.displayRows[rowIndex][this.$scope.sortColumn].text;
if (selectedTime &&
this.toiFormatter.validate(selectedTime) &&
event.altKey) {
this.conductor.timeOfInterest(this.toiFormatter.parse(selectedTime));
}
}
};
return MCTTableController;
}

View File

@ -35,8 +35,8 @@ define(
* @param telemetryFormatter
* @constructor
*/
function RealtimeTableController($scope, telemetryHandler, telemetryFormatter) {
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
function RealtimeTableController($scope, telemetryHandler, telemetryFormatter, openmct) {
TableController.call(this, $scope, telemetryHandler, telemetryFormatter, openmct);
this.maxRows = 100000;
}

View File

@ -43,7 +43,8 @@ define(
function TelemetryTableController(
$scope,
telemetryHandler,
telemetryFormatter
telemetryFormatter,
openmct
) {
var self = this;
@ -54,6 +55,7 @@ define(
this.table = new TableConfiguration($scope.domainObject,
telemetryFormatter);
this.changeListeners = [];
this.conductor = openmct.conductor;
$scope.rows = [];
@ -63,13 +65,35 @@ define(
self.registerChangeListeners();
});
this.destroy = this.destroy.bind(this);
// Unsubscribe when the plot is destroyed
this.$scope.$on("$destroy", this.destroy.bind(this));
this.$scope.$on("$destroy", this.destroy);
this.timeColumns = [];
this.sortByTimeSystem = this.sortByTimeSystem.bind(this);
this.conductor.on('timeSystem', this.sortByTimeSystem);
this.conductor.off('timeSystem', this.sortByTimeSystem);
}
/**
* @private
* Based on the selected time system, find a matching domain column
* to sort by. By default will just match on key.
* @param timeSystem
*/
TelemetryTableController.prototype.sortByTimeSystem = function (timeSystem) {
var scope = this.$scope;
scope.defaultSort = undefined;
if (timeSystem) {
this.table.columns.forEach(function (column) {
if (column.domainMetadata && column.domainMetadata.key === timeSystem.metadata.key) {
scope.defaultSort = column.getTitle();
}
});
}
};
TelemetryTableController.prototype.unregisterChangeListeners = function () {
this.changeListeners.forEach(function (listener) {
return listener && listener();
@ -148,20 +172,37 @@ define(
this.setup();
};
TelemetryTableController.prototype.populateColumns = function (telemetryMetadata) {
this.table.populateColumns(telemetryMetadata);
//Identify time columns
telemetryMetadata.forEach(function (metadatum) {
//Push domains first
(metadatum.domains || []).forEach(function (domainMetadata) {
this.timeColumns.push(domainMetadata.name);
}.bind(this));
}.bind(this));
var timeSystem = this.conductor.timeSystem();
if (timeSystem) {
this.sortByTimeSystem(timeSystem);
}
};
/**
* Setup table columns based on domain object metadata
*/
TelemetryTableController.prototype.setup = function () {
var handle = this.handle,
table = this.table,
self = this;
if (handle) {
this.timeColumns = [];
handle.promiseTelemetryObjects().then(function () {
self.$scope.headers = [];
self.$scope.rows = [];
table.populateColumns(handle.getMetadata());
self.populateColumns(handle.getMetadata());
self.filterColumns();
// When table column configuration changes, (due to being

View File

@ -86,14 +86,25 @@ define(
'$timeout',
'$element',
'exportService',
'formatService',
'openmct',
MCTTableController
],
controllerAs: "table",
scope: {
headers: "=",
rows: "=",
enableFilter: "=?",
enableSort: "=?",
autoScroll: "=?"
autoScroll: "=?",
// Used to indicate which columns contain time data. This
// will be used for determining when the table is sorted
// by the column that can be used for time conductor
// time of interest.
timeColumns: "=?",
// Indicate a column to sort on. Allows control of sort
// via configuration (eg. for default sort column).
sortColumn: "=?"
}
};
}

View File

@ -37,6 +37,7 @@ define(
mockAngularTimeout,
mockTimeoutHandle,
watches,
mockConductor,
controller;
function promise(value) {
@ -47,6 +48,12 @@ define(
};
}
function getCallback(target, event) {
return target.calls.filter(function (call) {
return call.args[0] === event;
})[0].args[1];
}
beforeEach(function () {
watches = {};
mockScope = jasmine.createSpyObj('scope', [
@ -108,13 +115,22 @@ define(
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
mockTelemetryHandle.request.andReturn(promise(undefined));
mockTelemetryHandle.getTelemetryObjects.andReturn([]);
mockTelemetryHandle.getMetadata.andReturn([]);
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
'handle'
]);
mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter, mockAngularTimeout);
mockConductor = jasmine.createSpyObj("conductor", [
"timeSystem",
"on",
"off"
]);
controller = new TableController(mockScope, mockTelemetryHandler,
mockTelemetryFormatter, mockAngularTimeout, {conductor: mockConductor});
controller.table = mockTable;
controller.handle = mockTelemetryHandle;
});
@ -233,6 +249,60 @@ define(
});
});
describe('After populating columns', function () {
var metadata;
beforeEach(function () {
metadata = [{domains: [{name: 'time domain 1'}, {name: 'time domain 2'}]}, {domains: [{name: 'time domain 3'}, {name: 'time domain 4'}]}];
controller.populateColumns(metadata);
});
it('Automatically identifies time columns', function () {
expect(controller.timeColumns.length).toBe(4);
expect(controller.timeColumns[0]).toBe('time domain 1');
});
it('Automatically sorts by time column that matches current' +
' time system', function () {
var key = 'time_domain_1',
name = 'time domain 1',
mockTimeSystem = {
metadata: {
key: key
}
};
mockTable.columns = [
{
domainMetadata: {
key: key
},
getTitle: function () {
return name;
}
},
{
domainMetadata: {
key: 'anotherColumn'
},
getTitle: function () {
return 'some other column';
}
},
{
domainMetadata: {
key: 'thirdColumn'
},
getTitle: function () {
return 'a third column';
}
}
];
expect(mockConductor.on).toHaveBeenCalledWith('timeSystem', jasmine.any(Function));
getCallback(mockConductor.on, 'timeSystem')(mockTimeSystem);
expect(controller.$scope.defaultSort).toBe(name);
});
});
describe('Yields thread', function () {
var mockSeries,
mockRow;

View File

@ -23,9 +23,10 @@
define(
[
"zepto",
"moment",
"../../src/controllers/MCTTableController"
],
function ($, MCTTableController) {
function ($, moment, MCTTableController) {
var MOCK_ELEMENT_TEMPLATE =
'<div><div class="l-view-section scrolling">' +
@ -40,7 +41,10 @@ define(
watches,
mockTimeout,
mockElement,
mockExportService;
mockExportService,
mockConductor,
mockFormatService,
mockFormat;
function promise(value) {
return {
@ -50,6 +54,12 @@ define(
};
}
function getCallback(target, event) {
return target.calls.filter(function (call) {
return call.args[0] === event;
})[0].args[1];
}
beforeEach(function () {
watches = {};
@ -67,15 +77,33 @@ define(
'exportCSV'
]);
mockConductor = jasmine.createSpyObj('conductor', [
'bounds',
'timeOfInterest',
'timeSystem',
'on',
'off'
]);
mockScope.displayHeaders = true;
mockTimeout = jasmine.createSpy('$timeout');
mockTimeout.andReturn(promise(undefined));
mockFormat = jasmine.createSpyObj('formatter', [
'parse',
'format'
]);
mockFormatService = jasmine.createSpyObj('formatService', [
'getFormat'
]);
mockFormatService.getFormat.andReturn(mockFormat);
controller = new MCTTableController(
mockScope,
mockTimeout,
mockElement,
mockExportService
mockExportService,
mockFormatService,
{conductor: mockConductor}
);
spyOn(controller, 'setVisibleRows').andCallThrough();
});
@ -86,6 +114,133 @@ define(
expect(mockScope.$watch).toHaveBeenCalledWith('rows', jasmine.any(Function));
});
it('destroys listeners on destruction', function () {
expect(mockScope.$on).toHaveBeenCalledWith('$destroy', controller.destroyConductorListeners);
getCallback(mockScope.$on, '$destroy')();
expect(mockConductor.off).toHaveBeenCalledWith('timeSystem', controller.changeTimeSystem);
expect(mockConductor.off).toHaveBeenCalledWith('timeOfInterest', controller.setTimeOfInterest);
expect(mockConductor.off).toHaveBeenCalledWith('bounds', controller.changeBounds);
});
describe('The time of interest', function () {
var rowsAsc = [];
var rowsDesc = [];
beforeEach(function () {
rowsAsc = [
{
'col1': {'text': 'row1 col1 match'},
'col2': {'text': '2012-10-31 00:00:00.000Z'},
'col3': {'text': 'row1 col3'}
},
{
'col1': {'text': 'row2 col1 match'},
'col2': {'text': '2012-11-01 00:00:00.000Z'},
'col3': {'text': 'row2 col3'}
},
{
'col1': {'text': 'row3 col1'},
'col2': {'text': '2012-11-03 00:00:00.000Z'},
'col3': {'text': 'row3 col3'}
},
{
'col1': {'text': 'row3 col1'},
'col2': {'text': '2012-11-04 00:00:00.000Z'},
'col3': {'text': 'row3 col3'}
}
];
rowsDesc = [
{
'col1': {'text': 'row1 col1 match'},
'col2': {'text': '2012-11-02 00:00:00.000Z'},
'col3': {'text': 'row1 col3'}
},
{
'col1': {'text': 'row2 col1 match'},
'col2': {'text': '2012-11-01 00:00:00.000Z'},
'col3': {'text': 'row2 col3'}
},
{
'col1': {'text': 'row3 col1'},
'col2': {'text': '2012-10-30 00:00:00.000Z'},
'col3': {'text': 'row3 col3'}
},
{
'col1': {'text': 'row3 col1'},
'col2': {'text': '2012-10-29 00:00:00.000Z'},
'col3': {'text': 'row3 col3'}
}
];
mockScope.timeColumns = ['col2'];
mockScope.sortColumn = 'col2';
controller.toiFormatter = mockFormat;
});
it("is observed for changes", function () {
//Mock setting time columns
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
expect(mockConductor.on).toHaveBeenCalledWith('timeOfInterest',
jasmine.any(Function));
});
describe("causes corresponding row to be highlighted", function () {
it("when changed and rows sorted ascending", function () {
var testDate = "2012-11-02 00:00:00.000Z";
mockScope.rows = rowsAsc;
mockScope.displayRows = rowsAsc;
mockScope.sortDirection = 'asc';
var toi = moment.utc(testDate).valueOf();
mockFormat.parse.andReturn(toi);
mockFormat.format.andReturn(testDate);
//mock setting the timeColumns parameter
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
var toiCallback = getCallback(mockConductor.on, 'timeOfInterest');
toiCallback(toi);
expect(mockScope.toiRowIndex).toBe(2);
});
it("when changed and rows sorted descending", function () {
var testDate = "2012-10-31 00:00:00.000Z";
mockScope.rows = rowsDesc;
mockScope.displayRows = rowsDesc;
mockScope.sortDirection = 'desc';
var toi = moment.utc(testDate).valueOf();
mockFormat.parse.andReturn(toi);
mockFormat.format.andReturn(testDate);
//mock setting the timeColumns parameter
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
var toiCallback = getCallback(mockConductor.on, 'timeOfInterest');
toiCallback(toi);
expect(mockScope.toiRowIndex).toBe(2);
});
it("when rows are set and sorted ascending", function () {
var testDate = "2012-11-02 00:00:00.000Z";
mockScope.sortDirection = 'asc';
var toi = moment.utc(testDate).valueOf();
mockFormat.parse.andReturn(toi);
mockFormat.format.andReturn(testDate);
mockConductor.timeOfInterest.andReturn(toi);
//mock setting the timeColumns parameter
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
//Mock setting the rows on scope
var rowsCallback = getCallback(mockScope.$watch, 'rows');
rowsCallback(rowsAsc);
expect(mockScope.toiRowIndex).toBe(2);
});
});
});
describe('rows', function () {
var testRows = [];
beforeEach(function () {
@ -132,7 +287,7 @@ define(
});
it('Supports adding rows individually', function () {
var addRowFunc = mockScope.$on.calls[mockScope.$on.calls.length - 2].args[1],
var addRowFunc = getCallback(mockScope.$on, 'add:row'),
row4 = {
'col1': {'text': 'row3 col1'},
'col2': {'text': 'ghi'},
@ -146,7 +301,7 @@ define(
});
it('Supports removing rows individually', function () {
var removeRowFunc = mockScope.$on.calls[mockScope.$on.calls.length - 1].args[1];
var removeRowFunc = getCallback(mockScope.$on, 'remove:row');
controller.setRows(testRows);
expect(mockScope.displayRows.length).toBe(3);
removeRowFunc(undefined, 2);
@ -173,6 +328,10 @@ define(
describe('sorting', function () {
var sortedRows;
beforeEach(function () {
sortedRows = [];
});
it('Sorts rows ascending', function () {
mockScope.sortColumn = 'col1';
mockScope.sortDirection = 'asc';
@ -204,6 +363,20 @@ define(
expect(sortedRows[2].col2.text).toEqual('abc');
});
it('Allows sort column to be changed externally by ' +
'setting or changing sortBy attribute', function () {
mockScope.displayRows = testRows;
var sortByCB = getCallback(mockScope.$watch, 'sortColumn');
sortByCB('col2');
expect(mockScope.sortDirection).toEqual('asc');
expect(mockScope.displayRows[0].col2.text).toEqual('abc');
expect(mockScope.displayRows[1].col2.text).toEqual('def');
expect(mockScope.displayRows[2].col2.text).toEqual('ghi');
});
// https://github.com/nasa/openmct/issues/910
it('updates visible rows in scope', function () {
var oldRows;
@ -369,8 +542,6 @@ define(
});
});
});
});
});

View File

@ -36,6 +36,7 @@ define(
mockConfiguration,
watches,
mockTableRow,
mockConductor,
controller;
function promise(value) {
@ -106,7 +107,8 @@ define(
'getDatum',
'promiseTelemetryObjects',
'getTelemetryObjects',
'request'
'request',
'getMetadata'
]);
// Arbitrary array with non-zero length, contents are not
@ -115,13 +117,22 @@ define(
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
mockTelemetryHandle.getDatum.andReturn({});
mockTelemetryHandle.request.andReturn(promise(undefined));
mockTelemetryHandle.getMetadata.andReturn([]);
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
'handle'
]);
mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter);
mockConductor = jasmine.createSpyObj('conductor', [
'on',
'off',
'bounds',
'timeSystem',
'timeOfInterest'
]);
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter, {conductor: mockConductor});
controller.table = mockTable;
controller.handle = mockTelemetryHandle;
});

View File

@ -75,12 +75,6 @@ define(['EventEmitter'], function (EventEmitter) {
return true;
};
function throwOnError(validationResult) {
if (validationResult !== true) {
throw new Error(validationResult);
}
}
/**
* Get or set the follow mode of the time conductor. In follow mode the
* time conductor ticks, regularly updating the bounds from a timing
@ -127,8 +121,12 @@ define(['EventEmitter'], function (EventEmitter) {
*/
TimeConductor.prototype.bounds = function (newBounds) {
if (arguments.length > 0) {
throwOnError(this.validateBounds(newBounds));
this.boundsVal = newBounds;
var validationResult = this.validateBounds(newBounds);
if (validationResult !== true) {
throw new Error(validationResult);
}
//Create a copy to avoid direct mutation of conductor bounds
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
/**
* The start time, end time, or both have been updated.
* @event bounds
@ -136,8 +134,15 @@ define(['EventEmitter'], function (EventEmitter) {
* @property {TimeConductorBounds} bounds
*/
this.emit('bounds', this.boundsVal);
// If a bounds change results in a TOI outside of the current
// bounds, unset it
if (this.toi < newBounds.start || this.toi > newBounds.end) {
this.timeOfInterest(undefined);
}
}
return this.boundsVal;
//Return a copy to prevent direct mutation of time conductor bounds.
return JSON.parse(JSON.stringify(this.boundsVal));
};
/**
@ -164,9 +169,6 @@ define(['EventEmitter'], function (EventEmitter) {
* Time System
* */
this.emit('timeSystem', this.system);
// Do something with bounds here. Try and convert between
// time systems? Or just set defaults when time system changes?
// eg.
this.bounds(bounds);
} else if (arguments.length === 1) {
throw new Error('Must set bounds when changing time system');
@ -177,7 +179,8 @@ 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.
* time conductor or from other views.The time of interest can
* effectively be unset by assigning a value of 'undefined'.
* @fires module:openmct.TimeConductor~timeOfInterest
* @param newTOI
* @returns {number} the current time of interest

View File

@ -52,19 +52,19 @@ define(['./TimeConductor'], function (TimeConductor) {
bounds = {start: 0, end: 1};
expect(tc.bounds()).not.toBe(bounds);
expect(tc.bounds.bind(tc, bounds)).not.toThrow();
expect(tc.bounds()).toBe(bounds);
expect(tc.bounds()).toEqual(bounds);
});
it("Disallows setting of invalid bounds", function () {
bounds = {start: 1, end: 0};
expect(tc.bounds()).not.toBe(bounds);
expect(tc.bounds()).not.toEqual(bounds);
expect(tc.bounds.bind(tc, bounds)).toThrow();
expect(tc.bounds()).not.toBe(bounds);
expect(tc.bounds()).not.toEqual(bounds);
bounds = {start: 1};
expect(tc.bounds()).not.toBe(bounds);
expect(tc.bounds()).not.toEqual(bounds);
expect(tc.bounds.bind(tc, bounds)).toThrow();
expect(tc.bounds()).not.toBe(bounds);
expect(tc.bounds()).not.toEqual(bounds);
});
it("Allows setting of time system with bounds", function () {
@ -106,5 +106,17 @@ define(['./TimeConductor'], function (TimeConductor) {
tc.follow(follow);
expect(eventListener).toHaveBeenCalledWith(follow);
});
it("If bounds are set and TOI lies inside them, do not change TOI", function () {
tc.timeOfInterest(6);
tc.bounds({start: 1, end: 10});
expect(tc.timeOfInterest()).toEqual(6);
});
it("If bounds are set and TOI lies outside them, reset TOI", function () {
tc.timeOfInterest(11);
tc.bounds({start: 1, end: 10});
expect(tc.timeOfInterest()).toBeUndefined();
});
});
});