[Time Conductor] Remove previous time conductor. Fixes #1234
This commit is contained in:
161
platform/features/conductor/core/bundle.js
Normal file
161
platform/features/conductor/core/bundle.js
Normal file
@@ -0,0 +1,161 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/ui/TimeConductorViewService",
|
||||
"./src/ui/TimeConductorController",
|
||||
"./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",
|
||||
"text!./res/templates/time-of-interest.html",
|
||||
"legacyRegistry"
|
||||
], function (
|
||||
TimeConductorViewService,
|
||||
TimeConductorController,
|
||||
ConductorAxisController,
|
||||
ConductorTOIController,
|
||||
TimeOfInterestController,
|
||||
MCTConductorAxis,
|
||||
NumberFormat,
|
||||
timeConductorTemplate,
|
||||
modeSelectorTemplate,
|
||||
modeMenuTemplate,
|
||||
timeOfInterest,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
legacyRegistry.register("platform/features/conductor/core", {
|
||||
"extensions": {
|
||||
"services": [
|
||||
{
|
||||
"key": "timeConductorViewService",
|
||||
"implementation": TimeConductorViewService,
|
||||
"depends": [
|
||||
"openmct",
|
||||
"timeSystems[]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "TimeConductorController",
|
||||
"implementation": TimeConductorController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$window",
|
||||
"$location",
|
||||
"openmct",
|
||||
"timeConductorViewService",
|
||||
"timeSystems[]",
|
||||
"formatService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "ConductorTOIController",
|
||||
"implementation": ConductorTOIController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"timeConductorViewService",
|
||||
"formatService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "TimeOfInterestController",
|
||||
"implementation": TimeOfInterestController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"formatService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctConductorAxis",
|
||||
"implementation": MCTConductorAxis,
|
||||
"depends": [
|
||||
"openmct",
|
||||
"formatService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"stylesheets": [
|
||||
{
|
||||
"stylesheetUrl": "css/time-conductor-espresso.css",
|
||||
"theme": "espresso"
|
||||
},
|
||||
{
|
||||
"stylesheetUrl": "css/time-conductor-snow.css",
|
||||
"theme": "snow"
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
"key": "conductor",
|
||||
"template": timeConductorTemplate
|
||||
},
|
||||
{
|
||||
"key": "mode-menu",
|
||||
"template": modeMenuTemplate
|
||||
},
|
||||
{
|
||||
"key": "mode-selector",
|
||||
"template": modeSelectorTemplate
|
||||
},
|
||||
{
|
||||
"key": "time-of-interest",
|
||||
"template": timeOfInterest
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
{
|
||||
"key": "time-conductor",
|
||||
"template": timeConductorTemplate
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"name": "D3: Data-Driven Documents",
|
||||
"version": "4.1.0",
|
||||
"author": "Mike Bostock",
|
||||
"description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.",
|
||||
"website": "https://d3js.org/",
|
||||
"copyright": "Copyright 2010-2016 Mike Bostock",
|
||||
"license": "BSD-3-Clause",
|
||||
"link": "https://github.com/d3/d3/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"formats": [
|
||||
{
|
||||
"key": "number",
|
||||
"implementation": NumberFormat
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
11
platform/features/conductor/core/res/sass/_constants.scss
Normal file
11
platform/features/conductor/core/res/sass/_constants.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
$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
|
||||
$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
|
||||
@@ -0,0 +1,483 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2015, 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.
|
||||
*****************************************************************************/
|
||||
@mixin toiLineHovEffects() {
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: $timeControllerToiLineColorHov;
|
||||
}
|
||||
}
|
||||
|
||||
.l-time-conductor-holder {
|
||||
border-top: 1px solid $colorInteriorBorder;
|
||||
min-width: 500px;
|
||||
padding-top: $interiorMargin;
|
||||
}
|
||||
|
||||
.time-conductor-icon {
|
||||
$c: $colorObjHdrIc;
|
||||
$d: 18px;
|
||||
height: $d !important;
|
||||
width: $d;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
@extend .ui-symbol;
|
||||
color: $c;
|
||||
content: $glyph-icon-brackets;
|
||||
font-size: $d;
|
||||
line-height: normal;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// Clock hands
|
||||
div[class*="hand"] {
|
||||
$handW: 2px;
|
||||
$handH: $d * 0.4;
|
||||
@include transform(translate(-50%, -50%));
|
||||
@include animation-iteration-count(infinite);
|
||||
@include animation-timing-function(linear);
|
||||
position: absolute;
|
||||
height: $handW;
|
||||
width: $handW;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 2;
|
||||
&:before {
|
||||
background: $colorObjHdrIc;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: -1px;
|
||||
}
|
||||
&.hand-little {
|
||||
z-index: 2;
|
||||
@include animation-duration(12s);
|
||||
&:before {
|
||||
height: ceil($handH * 0.7);
|
||||
}
|
||||
}
|
||||
&.hand-big {
|
||||
z-index: 1;
|
||||
@include animation-duration(1s);
|
||||
&:before {
|
||||
height: $handH;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l-time-conductor {
|
||||
$knobHOffset: 0px;
|
||||
$rangeValPad: $interiorMargin;
|
||||
$rangeValOffset: $sliderKnobW + $interiorMargin;
|
||||
$r1H: nth($ueTimeConductorH, 1);
|
||||
$r2H: nth($ueTimeConductorH, 2);
|
||||
$r3H: nth($ueTimeConductorH, 3);
|
||||
position: relative;
|
||||
|
||||
> .l-row-elem {
|
||||
// First order row elements
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mode-selector .s-menu-button,
|
||||
.time-delta {
|
||||
&:before {
|
||||
@extend .ui-symbol;
|
||||
}
|
||||
}
|
||||
|
||||
.time-delta {
|
||||
&:before {
|
||||
color: $colorTimeCondKeyBg;
|
||||
}
|
||||
}
|
||||
|
||||
.l-time-conductor-inputs-holder,
|
||||
.l-time-conductor-inputs-and-ticks,
|
||||
.l-time-conductor-zoom-w {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.l-time-conductor-inputs-holder {
|
||||
$iconCalendarW: 16px;
|
||||
$wBgColor: $colorBodyBg;
|
||||
|
||||
height: $r1H;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
.l-time-range-w {
|
||||
// Wraps a datetime text input field
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
.title {
|
||||
display: inline-block;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
&.start-w {
|
||||
@include background-image(linear-gradient(270deg, transparent, $wBgColor $ticksBlockerFadeW));
|
||||
padding-right: $ticksBlockerFadeW;
|
||||
.title:before {
|
||||
content: 'Start';
|
||||
}
|
||||
}
|
||||
&.end-w {
|
||||
@include background-image(linear-gradient(90deg, transparent, $wBgColor $ticksBlockerFadeW));
|
||||
padding-left: $ticksBlockerFadeW;
|
||||
right: 0;
|
||||
.title:before {
|
||||
content: 'End';
|
||||
}
|
||||
}
|
||||
.l-time-conductor-inputs {
|
||||
pointer-events: auto;
|
||||
}
|
||||
input[type="text"] {
|
||||
@include trans-prop-nice(padding, 250ms);
|
||||
}
|
||||
.time-range-input input[type="text"] {
|
||||
width: $timeCondInputTimeSysDefW;
|
||||
}
|
||||
.hrs-min-input input[type="text"] {
|
||||
width: $timeCondInputDeltaDefW;
|
||||
}
|
||||
.icon-calendar {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l-time-conductor-inputs-and-ticks {
|
||||
$c: $colorTimeCondTicks;
|
||||
height: $r1H;
|
||||
mct-conductor-axis {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.l-axis-holder {
|
||||
height: $r1H;
|
||||
position: absolute;
|
||||
left: nth($timeCondAxisLROffset, 1);
|
||||
right: nth($timeCondAxisLROffset, 2);
|
||||
svg {
|
||||
text-rendering: geometricPrecision;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
> g {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
path {
|
||||
// Line beneath ticks
|
||||
display: none;
|
||||
}
|
||||
line {
|
||||
// Tick marks
|
||||
stroke: $c;
|
||||
}
|
||||
text {
|
||||
// Tick labels
|
||||
fill: $c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.l-data-visualization-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 {
|
||||
align-items: center;
|
||||
margin-top: $interiorMargin;
|
||||
.l-time-conductor-zoom-w {
|
||||
@include justify-content(flex-end);
|
||||
.time-conductor-zoom {
|
||||
height: $r3H;
|
||||
min-width: 100px;
|
||||
width: 20%;
|
||||
}
|
||||
.time-conductor-zoom-current-range {
|
||||
color: $colorTick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Real-time, latest modes
|
||||
&.realtime-mode,
|
||||
&.lad-mode {
|
||||
.time-conductor-icon {
|
||||
&:before {
|
||||
color: $colorTimeCondKeyBg;
|
||||
}
|
||||
div[class*="hand"] {
|
||||
@include animation-name(clock-hands);
|
||||
&:before {
|
||||
background: $colorTimeCondKeyBg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l-time-conductor-inputs-holder {
|
||||
.l-time-range-input-w {
|
||||
input[type="text"]:not(.error) {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
&:hover,
|
||||
&:focus {
|
||||
@include nice-input();
|
||||
padding: $inputTextP;
|
||||
}
|
||||
}
|
||||
.icon-calendar {
|
||||
display: none;
|
||||
}
|
||||
&.start-date {
|
||||
display: none;
|
||||
}
|
||||
&.end-date {
|
||||
pointer-events: none;
|
||||
input[type="text"] {
|
||||
color: pullForward($colorTimeCondKeyBg, 5%);
|
||||
margin-right: $interiorMargin;
|
||||
tab-index: -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l-data-visualization {
|
||||
background: $colorTimeCondDataVisRtBg !important;
|
||||
}
|
||||
|
||||
.mode-selector .s-menu-button {
|
||||
$fg: $colorTimeCondKeyFg;
|
||||
@include btnSubtle($bg: $colorTimeCondKeyBg, $bgHov: pullForward($colorTimeCondKeyBg, $ltGamma), $fg: $colorTimeCondKeyFg);
|
||||
&:before {
|
||||
color: $fg !important;
|
||||
}
|
||||
;
|
||||
color: $fg !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed mode
|
||||
&.fixed-mode {
|
||||
$i: $glyph-icon-calendar;
|
||||
.time-conductor-icon div[class*="hand"] {
|
||||
&.hand-little {
|
||||
@include transform(rotate(120deg));
|
||||
}
|
||||
}
|
||||
.mode-selector .s-menu-button:before {
|
||||
content: $i;
|
||||
}
|
||||
.l-axis-holder {
|
||||
@include cursorGrab();
|
||||
}
|
||||
}
|
||||
|
||||
// Realtime mode
|
||||
&.realtime-mode {
|
||||
$i: $glyph-icon-clock;
|
||||
.time-conductor-icon div[class*="hand"] {
|
||||
@include animation-name(clock-hands);
|
||||
}
|
||||
.time-delta:before {
|
||||
content: $i;
|
||||
}
|
||||
.l-time-conductor-inputs-holder .l-time-range-w.end-w .title:before {
|
||||
content: 'Now';
|
||||
}
|
||||
.mode-selector .s-menu-button:before {
|
||||
content: $i;
|
||||
}
|
||||
}
|
||||
|
||||
// LAD mode
|
||||
&.lad-mode {
|
||||
$i: $glyph-icon-database;
|
||||
.time-conductor-icon div[class*="hand"] {
|
||||
@include animation-name(clock-hands-sticky);
|
||||
&.hand-big {
|
||||
@include animation-duration(5s);
|
||||
}
|
||||
&.hand-little {
|
||||
@include animation-duration(60s);
|
||||
}
|
||||
}
|
||||
.time-delta:before {
|
||||
content: $i;
|
||||
}
|
||||
.l-time-conductor-inputs-holder .l-time-range-w.end-w .title:before {
|
||||
content: 'LAD';
|
||||
}
|
||||
.mode-selector .s-menu-button:before {
|
||||
content: $i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************** MOBILE */
|
||||
|
||||
@include phoneandtablet {
|
||||
.l-time-conductor-holder {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
.super-menu.mini {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
.pane.menu-item-description {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include phone {
|
||||
.l-time-conductor {
|
||||
min-width: 0;
|
||||
.l-time-conductor-inputs-and-ticks {
|
||||
.l-time-conductor-inputs-holder {
|
||||
.l-time-range-w {
|
||||
background-image: none !important;
|
||||
}
|
||||
}
|
||||
mct-conductor-axis {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include phonePortrait {
|
||||
.l-time-conductor {
|
||||
.l-data-visualization,
|
||||
.l-time-conductor-zoom-w,
|
||||
.time-delta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.l-time-conductor-inputs-and-ticks {
|
||||
height: auto !important;
|
||||
.l-time-conductor-inputs-holder {
|
||||
position: relative;
|
||||
height: auto !important;
|
||||
|
||||
.l-time-range-w {
|
||||
background-image: none !important;
|
||||
display: block;
|
||||
height: auto !important;
|
||||
padding: 0 !important;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
&:not(:first-child) {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed mode
|
||||
&.fixed-mode {
|
||||
.l-time-conductor-inputs-and-ticks {
|
||||
.l-time-range-w {
|
||||
.title {
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Real-time, latest modes
|
||||
&.realtime-mode,
|
||||
&.lad-mode {
|
||||
.l-time-conductor-inputs-and-ticks {
|
||||
.l-time-range-w {
|
||||
&.start-w {
|
||||
display: none;
|
||||
}
|
||||
&.end-w {
|
||||
margin-top: 0;
|
||||
.end-date input[type="text"] {
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
271
platform/features/conductor/core/res/sass/_time-of-interest.scss
Normal file
271
platform/features/conductor/core/res/sass/_time-of-interest.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2015, 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.
|
||||
*****************************************************************************/
|
||||
@import "bourbon";
|
||||
@import "../../../../../commonUI/general/res/sass/constants";
|
||||
@import "../../../../../commonUI/general/res/sass/mixins";
|
||||
@import "../../../../../commonUI/general/res/sass/mobile/constants";
|
||||
@import "../../../../../commonUI/general/res/sass/mobile/mixins";
|
||||
@import "../../../../../commonUI/themes/espresso/res/sass/constants";
|
||||
@import "../../../../../commonUI/themes/espresso/res/sass/mixins";
|
||||
@import "../../../../../commonUI/general/res/sass/glyphs";
|
||||
@import "../../../../../commonUI/general/res/sass/icons";
|
||||
@import "constants";
|
||||
|
||||
// Thematic constants
|
||||
$colorTimeCondTicks: pullForward($colorBodyBg, 30%);
|
||||
$colorTimeCondKeyBg: #4e70dc;
|
||||
$colorTimeCondKeyFg: #fff;
|
||||
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 5%);
|
||||
$colorTimeCondDataVisRtBg: pushBack($colorTimeCondKeyBg, 10%);
|
||||
|
||||
// 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";
|
||||
@@ -0,0 +1,50 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2015, 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.
|
||||
*****************************************************************************/
|
||||
@import "bourbon";
|
||||
@import "../../../../../commonUI/general/res/sass/constants";
|
||||
@import "../../../../../commonUI/general/res/sass/mixins";
|
||||
@import "../../../../../commonUI/general/res/sass/mobile/constants";
|
||||
@import "../../../../../commonUI/general/res/sass/mobile/mixins";
|
||||
@import "../../../../../commonUI/themes/snow/res/sass/constants";
|
||||
@import "../../../../../commonUI/themes/snow/res/sass/mixins";
|
||||
@import "../../../../../commonUI/general/res/sass/glyphs";
|
||||
@import "../../../../../commonUI/general/res/sass/icons";
|
||||
@import "constants";
|
||||
|
||||
// Thematic constants
|
||||
$colorTimeCondTicks: pullForward($colorBodyBg, 30%);
|
||||
$colorTimeCondKeyBg: #6178dc;
|
||||
$colorTimeCondKeyFg: #fff;
|
||||
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 10%);
|
||||
$colorTimeCondDataVisRtBg: pushBack($colorTimeCondKeyBg, 30%);
|
||||
|
||||
// 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";
|
||||
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT Web includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="contents">
|
||||
<div class="pane left menu-items">
|
||||
<ul>
|
||||
<li ng-repeat="(key, metadata) in ngModel.options"
|
||||
ng-click="ngModel.selectedKey=key">
|
||||
<a ng-mouseover="ngModel.activeMetadata = metadata"
|
||||
ng-mouseleave="ngModel.activeMetadata = undefined"
|
||||
class="menu-item-a {{metadata.cssclass}}">
|
||||
{{metadata.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pane right menu-item-description">
|
||||
<div
|
||||
class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssclass}}"></div>
|
||||
<div class="desc-area title">
|
||||
{{ngModel.activeMetadata.name}}
|
||||
</div>
|
||||
<div class="desc-area description">
|
||||
{{ngModel.activeMetadata.description}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
<!--
|
||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT Web includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span ng-controller="ClickAwayController as modeController">
|
||||
<div class="s-menu-button"
|
||||
ng-click="modeController.toggle()">
|
||||
<span class="title-label">{{ngModel.options[ngModel.selectedKey]
|
||||
.label}}</span>
|
||||
</div>
|
||||
<div class="menu super-menu mini mode-selector-menu"
|
||||
ng-show="modeController.isActive()">
|
||||
<mct-include key="'mode-menu'"
|
||||
ng-model="ngModel">
|
||||
</mct-include>
|
||||
</div>
|
||||
</span>
|
||||
@@ -0,0 +1,132 @@
|
||||
<!-- 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"
|
||||
ng-class="{'status-panning': tcController.panning}">
|
||||
|
||||
<div class="flex-elem holder time-conductor-icon">
|
||||
<div class="hand-little"></div>
|
||||
<div class="hand-big"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
|
||||
<!-- Holds inputs and ticks -->
|
||||
<div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin">
|
||||
<form class="l-time-conductor-inputs-holder"
|
||||
ng-submit="tcController.setBounds(boundsModel)">
|
||||
<span class="l-time-range-w start-w">
|
||||
<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.setBounds(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.setDeltas(boundsModel)"
|
||||
field="'startDelta'"
|
||||
class="hrs-min-input">
|
||||
</mct-control>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="l-time-range-w end-w">
|
||||
<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.setBounds(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.setDeltas(boundsModel)"
|
||||
field="'endDelta'"
|
||||
class="hrs-min-input">
|
||||
</mct-control>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<input type="submit" class="hidden">
|
||||
</form>
|
||||
<mct-conductor-axis></mct-conductor-axis>
|
||||
</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">
|
||||
<mct-include
|
||||
key="'mode-selector'"
|
||||
ng-model="modeModel"
|
||||
class="holder flex-elem menus-up mode-selector">
|
||||
</mct-include>
|
||||
<mct-control
|
||||
key="'menu-button'"
|
||||
class="holder flex-elem menus-up time-system"
|
||||
structure="{
|
||||
text: timeSystemModel.selected.metadata.name,
|
||||
click: tcController.selectTimeSystemByKey,
|
||||
options: timeSystemModel.options
|
||||
}">
|
||||
</mct-control>
|
||||
<!-- Zoom control -->
|
||||
<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>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,89 @@
|
||||
/*****************************************************************************
|
||||
* 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(['./TickSource'], function (TickSource) {
|
||||
/**
|
||||
* @implements TickSource
|
||||
* @constructor
|
||||
*/
|
||||
function LocalClock($timeout, period) {
|
||||
TickSource.call(this);
|
||||
|
||||
this.metadata = {
|
||||
key: 'local',
|
||||
mode: 'realtime',
|
||||
cssclass: 'icon-clock',
|
||||
label: 'Real-time',
|
||||
name: 'Real-time Mode',
|
||||
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
|
||||
};
|
||||
|
||||
this.period = period;
|
||||
this.$timeout = $timeout;
|
||||
this.timeoutHandle = undefined;
|
||||
}
|
||||
|
||||
LocalClock.prototype = Object.create(TickSource.prototype);
|
||||
|
||||
LocalClock.prototype.start = function () {
|
||||
this.timeoutHandle = this.$timeout(this.tick.bind(this), this.period);
|
||||
};
|
||||
|
||||
LocalClock.prototype.stop = function () {
|
||||
if (this.timeoutHandle) {
|
||||
this.$timeout.cancel(this.timeoutHandle);
|
||||
}
|
||||
};
|
||||
|
||||
LocalClock.prototype.tick = function () {
|
||||
var now = Date.now();
|
||||
this.listeners.forEach(function (listener) {
|
||||
listener(now);
|
||||
});
|
||||
this.timeoutHandle = this.$timeout(this.tick.bind(this), this.period);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a listener for the local clock. When it ticks, the local
|
||||
* clock will provide the current local system time
|
||||
*
|
||||
* @param listener
|
||||
* @returns {function} a function for deregistering the provided listener
|
||||
*/
|
||||
LocalClock.prototype.listen = function (listener) {
|
||||
var listeners = this.listeners;
|
||||
listeners.push(listener);
|
||||
|
||||
if (listeners.length === 1) {
|
||||
this.start();
|
||||
}
|
||||
|
||||
return function () {
|
||||
listeners.splice(listeners.indexOf(listener));
|
||||
if (listeners.length === 0) {
|
||||
this.stop();
|
||||
}
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
return LocalClock;
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/*****************************************************************************
|
||||
* 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(["./LocalClock"], function (LocalClock) {
|
||||
describe("The LocalClock class", function () {
|
||||
var clock,
|
||||
mockTimeout,
|
||||
timeoutHandle = {};
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeout = jasmine.createSpy("timeout");
|
||||
mockTimeout.andReturn(timeoutHandle);
|
||||
mockTimeout.cancel = jasmine.createSpy("cancel");
|
||||
|
||||
clock = new LocalClock(mockTimeout, 0);
|
||||
clock.start();
|
||||
});
|
||||
|
||||
it("calls listeners on tick with current time", function () {
|
||||
var mockListener = jasmine.createSpy("listener");
|
||||
clock.listen(mockListener);
|
||||
clock.tick();
|
||||
expect(mockListener).toHaveBeenCalledWith(jasmine.any(Number));
|
||||
});
|
||||
|
||||
it("stops ticking when stop is called", function () {
|
||||
clock.stop();
|
||||
expect(mockTimeout.cancel).toHaveBeenCalledWith(timeoutHandle);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
/**
|
||||
* A tick source is an event generator such as a timing signal, or
|
||||
* indicator of data availability, which can be used to advance the Time
|
||||
* Conductor. Usage is simple, a listener registers a callback which is
|
||||
* invoked when this source 'ticks'.
|
||||
*
|
||||
* @interface
|
||||
* @constructor
|
||||
*/
|
||||
function TickSource() {
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callback Function to be called when this tick source ticks.
|
||||
* @returns an 'unlisten' function that will remove the callback from
|
||||
* the registered listeners
|
||||
*/
|
||||
TickSource.prototype.listen = function (callback) {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
return TickSource;
|
||||
});
|
||||
107
platform/features/conductor/core/src/timeSystems/TimeSystem.js
Normal file
107
platform/features/conductor/core/src/timeSystems/TimeSystem.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
/**
|
||||
* @interface
|
||||
* @constructor
|
||||
*/
|
||||
function TimeSystem() {
|
||||
/**
|
||||
* @typedef TimeSystemMetadata
|
||||
* @property {string} key
|
||||
* @property {string} name
|
||||
* @property {string} description
|
||||
*
|
||||
* @type {TimeSystemMetadata}
|
||||
*/
|
||||
this.metadata = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time formats are defined as extensions. Time systems that implement
|
||||
* this interface should provide an array of format keys supported by them.
|
||||
*
|
||||
* @returns {string[]} An array of time format keys
|
||||
*/
|
||||
TimeSystem.prototype.formats = function () {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef DeltaFormat
|
||||
* @property {string} type the type of MctControl used to represent this
|
||||
* field. Typically 'datetime-field' for UTC based dates, or 'textfield'
|
||||
* otherwise
|
||||
* @property {string} [format] An optional field specifying the
|
||||
* Format to use for delta fields in this time system.
|
||||
*/
|
||||
/**
|
||||
* Specifies a format for deltas in this time system.
|
||||
*
|
||||
* @returns {DeltaFormat} a delta format specifier
|
||||
*/
|
||||
TimeSystem.prototype.deltaFormat = function () {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the tick sources supported by this time system. Tick sources
|
||||
* are event generators that can be used to advance the time conductor
|
||||
* @returns {TickSource[]} The tick sources supported by this time system.
|
||||
*/
|
||||
TimeSystem.prototype.tickSources = 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.
|
||||
*/
|
||||
TimeSystem.prototype.defaults = function () {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
TimeSystem.prototype.isUTCBased = function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
return TimeSystem;
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,178 @@
|
||||
/*****************************************************************************
|
||||
* 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([
|
||||
'./ConductorAxisController',
|
||||
'zepto',
|
||||
'd3'
|
||||
], function (
|
||||
ConductorAxisController,
|
||||
$,
|
||||
d3
|
||||
) {
|
||||
describe("The ConductorAxisController", function () {
|
||||
var controller,
|
||||
mockConductor,
|
||||
mockConductorViewService,
|
||||
mockFormatService,
|
||||
mockScope,
|
||||
mockElement,
|
||||
mockTarget,
|
||||
mockBounds,
|
||||
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", [
|
||||
"$on"
|
||||
]);
|
||||
|
||||
//Add some HTML elements
|
||||
mockTarget = {
|
||||
offsetWidth: 0,
|
||||
offsetHeight: 0
|
||||
};
|
||||
mockElement = {
|
||||
firstChild: mockTarget
|
||||
};
|
||||
mockBounds = {
|
||||
start: 100,
|
||||
end: 200
|
||||
};
|
||||
mockConductor = jasmine.createSpyObj("conductor", [
|
||||
"timeSystem",
|
||||
"bounds",
|
||||
"on",
|
||||
"off",
|
||||
"follow"
|
||||
]);
|
||||
mockConductor.bounds.andReturn(mockBounds);
|
||||
|
||||
mockFormatService = jasmine.createSpyObj("formatService", [
|
||||
"getFormat"
|
||||
]);
|
||||
|
||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
||||
"on",
|
||||
"off",
|
||||
"emit"
|
||||
]);
|
||||
|
||||
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", controller.changeTimeSystem);
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
||||
});
|
||||
|
||||
it("on scope destruction, deregisters listeners", function () {
|
||||
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 () {
|
||||
it("uses a UTC scale for UTC time systems", function () {
|
||||
mockTimeSystem.isUTCBased.andReturn(true);
|
||||
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);
|
||||
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);
|
||||
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 () {
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
54
platform/features/conductor/core/src/ui/MctConductorAxis.js
Normal file
54
platform/features/conductor/core/src/ui/MctConductorAxis.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/*****************************************************************************
|
||||
* 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(['./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.
|
||||
*/
|
||||
|
||||
return {
|
||||
controller: [
|
||||
'openmct',
|
||||
'formatService',
|
||||
'timeConductorViewService',
|
||||
'$scope',
|
||||
'$element',
|
||||
ConductorAxisController
|
||||
],
|
||||
controllerAs: 'axis',
|
||||
|
||||
restrict: 'E',
|
||||
priority: 1000,
|
||||
|
||||
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;
|
||||
});
|
||||
53
platform/features/conductor/core/src/ui/NumberFormat.js
Normal file
53
platform/features/conductor/core/src/ui/NumberFormat.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* Formatter for basic numbers. Provides basic support for non-UTC
|
||||
* numbering systems
|
||||
*
|
||||
* @implements {Format}
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/formats
|
||||
*/
|
||||
function NumberFormat() {
|
||||
}
|
||||
|
||||
NumberFormat.prototype.format = function (value) {
|
||||
if (isNaN(value)) {
|
||||
return '';
|
||||
} else {
|
||||
return '' + value;
|
||||
}
|
||||
};
|
||||
|
||||
NumberFormat.prototype.parse = function (text) {
|
||||
return parseFloat(text);
|
||||
};
|
||||
|
||||
NumberFormat.prototype.validate = function (text) {
|
||||
return !isNaN(text);
|
||||
};
|
||||
|
||||
return NumberFormat;
|
||||
});
|
||||
49
platform/features/conductor/core/src/ui/NumberFormatSpec.js
Normal file
49
platform/features/conductor/core/src/ui/NumberFormatSpec.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./NumberFormat'], function (NumberFormat) {
|
||||
describe("The NumberFormat class", function () {
|
||||
var format;
|
||||
beforeEach(function () {
|
||||
format = new NumberFormat();
|
||||
});
|
||||
|
||||
it("The format function takes a string and produces a number", function () {
|
||||
var text = format.format(1);
|
||||
expect(text).toBe("1");
|
||||
expect(typeof text).toBe("string");
|
||||
});
|
||||
|
||||
it("The parse function takes a string and produces a number", function () {
|
||||
var number = format.parse("1");
|
||||
expect(number).toBe(1);
|
||||
expect(typeof number).toBe("number");
|
||||
});
|
||||
|
||||
it("validates that the input is a number", function () {
|
||||
expect(format.validate("1")).toBe(true);
|
||||
expect(format.validate(1)).toBe(true);
|
||||
expect(format.validate("1.1")).toBe(true);
|
||||
expect(format.validate("abc")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,470 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
[
|
||||
'./TimeConductorValidation'
|
||||
],
|
||||
function (TimeConductorValidation) {
|
||||
var SEARCH = {
|
||||
MODE: 'tc.mode',
|
||||
TIME_SYSTEM: 'tc.timeSystem',
|
||||
START_BOUND: 'tc.startBound',
|
||||
END_BOUND: 'tc.endBound',
|
||||
START_DELTA: 'tc.startDelta',
|
||||
END_DELTA: 'tc.endDelta'
|
||||
};
|
||||
|
||||
/**
|
||||
* 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, $location, openmct, conductorViewService, timeSystems, formatService) {
|
||||
|
||||
var self = this;
|
||||
|
||||
//Bind all class functions to 'this'
|
||||
Object.keys(TimeConductorController.prototype).filter(function (key) {
|
||||
return typeof TimeConductorController.prototype[key] === 'function';
|
||||
}).forEach(function (key) {
|
||||
self[key] = self[key].bind(self);
|
||||
});
|
||||
|
||||
this.$scope = $scope;
|
||||
this.$window = $window;
|
||||
this.$location = $location;
|
||||
this.conductorViewService = conductorViewService;
|
||||
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) {
|
||||
return timeSystemConstructor();
|
||||
});
|
||||
|
||||
this.initializeScope();
|
||||
var searchParams = JSON.parse(JSON.stringify(this.$location.search()));
|
||||
//Set bounds, time systems, deltas, on conductor from URL
|
||||
this.setStateFromSearchParams(searchParams);
|
||||
|
||||
//Set the initial state of the UI from the conductor state
|
||||
var timeSystem = this.conductor.timeSystem();
|
||||
if (timeSystem) {
|
||||
this.changeTimeSystem(this.conductor.timeSystem());
|
||||
}
|
||||
|
||||
var deltas = this.conductorViewService.deltas();
|
||||
if (deltas) {
|
||||
this.setFormFromDeltas(deltas);
|
||||
}
|
||||
|
||||
var bounds = this.conductor.bounds();
|
||||
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
|
||||
this.changeBounds(bounds);
|
||||
}
|
||||
|
||||
//Listen for changes to URL and update state if necessary
|
||||
this.$scope.$on('$routeUpdate', function () {
|
||||
this.setStateFromSearchParams(this.$location.search());
|
||||
}.bind(this));
|
||||
|
||||
//Respond to any subsequent conductor changes
|
||||
this.conductor.on('bounds', this.changeBounds);
|
||||
this.conductor.on('timeSystem', this.changeTimeSystem);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.initializeScope = function () {
|
||||
//Set time Conductor bounds in the form
|
||||
this.$scope.boundsModel = this.conductor.bounds();
|
||||
|
||||
//If conductor has a time system selected already, populate the
|
||||
//form from it
|
||||
this.$scope.timeSystemModel = {};
|
||||
|
||||
//Represents the various modes, and the currently selected mode
|
||||
//in the view
|
||||
this.$scope.modeModel = {
|
||||
options: this.conductorViewService.availableModes()
|
||||
};
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
TimeConductorController.prototype.setStateFromSearchParams = function (searchParams) {
|
||||
//Set mode from url if changed
|
||||
if (searchParams[SEARCH.MODE] === undefined ||
|
||||
searchParams[SEARCH.MODE] !== this.$scope.modeModel.selectedKey) {
|
||||
this.setMode(searchParams[SEARCH.MODE] || "fixed");
|
||||
}
|
||||
|
||||
if (searchParams[SEARCH.TIME_SYSTEM] &&
|
||||
searchParams[SEARCH.TIME_SYSTEM] !== this.conductor.timeSystem().metadata.key) {
|
||||
//Will select the specified time system on the conductor
|
||||
this.selectTimeSystemByKey(searchParams[SEARCH.TIME_SYSTEM]);
|
||||
}
|
||||
|
||||
var validDeltas = searchParams[SEARCH.MODE] !== 'fixed' &&
|
||||
searchParams[SEARCH.START_DELTA] &&
|
||||
searchParams[SEARCH.END_DELTA] &&
|
||||
!isNaN(searchParams[SEARCH.START_DELTA]) &&
|
||||
!isNaN(searchParams[SEARCH.END_DELTA]);
|
||||
|
||||
if (validDeltas) {
|
||||
//Sets deltas from some form model
|
||||
this.setDeltas({
|
||||
startDelta: parseInt(searchParams[SEARCH.START_DELTA]),
|
||||
endDelta: parseInt(searchParams[SEARCH.END_DELTA])
|
||||
});
|
||||
}
|
||||
|
||||
var validBounds = searchParams[SEARCH.MODE] === 'fixed' &&
|
||||
searchParams[SEARCH.START_BOUND] &&
|
||||
searchParams[SEARCH.END_BOUND] &&
|
||||
!isNaN(searchParams[SEARCH.START_BOUND]) &&
|
||||
!isNaN(searchParams[SEARCH.END_BOUND]);
|
||||
|
||||
if (validBounds) {
|
||||
this.conductor.bounds({
|
||||
start: parseInt(searchParams[SEARCH.START_BOUND]),
|
||||
end: parseInt(searchParams[SEARCH.END_BOUND])
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.destroy = function () {
|
||||
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);
|
||||
if (this.conductorViewService.mode() === 'fixed') {
|
||||
//Set bounds in URL on change
|
||||
this.$location.search(SEARCH.START_BOUND, bounds.start);
|
||||
this.$location.search(SEARCH.END_BOUND, bounds.end);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {TimeConductorBounds}
|
||||
*/
|
||||
TimeConductorController.prototype.setFormFromBounds = function (bounds) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
//Synchronize scope with time system on mode
|
||||
this.$scope.timeSystemModel.options =
|
||||
this.conductorViewService.availableTimeSystems()
|
||||
.map(function (t) {
|
||||
return t.metadata;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* When the deltas change, update the values in the UI
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.setFormFromDeltas = function (deltas) {
|
||||
this.$scope.boundsModel.startDelta = deltas.start;
|
||||
this.$scope.boundsModel.endDelta = deltas.end;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the form when time system changes.
|
||||
* @param {TimeSystem} timeSystem
|
||||
*/
|
||||
TimeConductorController.prototype.setFormFromTimeSystem = function (timeSystem) {
|
||||
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.
|
||||
* @param formModel
|
||||
*/
|
||||
TimeConductorController.prototype.setBounds = function (boundsModel) {
|
||||
this.conductor.bounds({
|
||||
start: boundsModel.start,
|
||||
end: boundsModel.end
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the delta values in the form change. Validates and
|
||||
* sets the new deltas on the Mode.
|
||||
* @param boundsModel
|
||||
* @see TimeConductorMode
|
||||
*/
|
||||
TimeConductorController.prototype.setDeltas = function (boundsFormModel) {
|
||||
var deltas = {
|
||||
start: boundsFormModel.startDelta,
|
||||
end: boundsFormModel.endDelta
|
||||
};
|
||||
if (this.validation.validateStartDelta(deltas.start) && this.validation.validateEndDelta(deltas.end)) {
|
||||
//Sychronize deltas between form and mode
|
||||
this.conductorViewService.deltas(deltas);
|
||||
|
||||
//Set Deltas in URL on change
|
||||
this.$location.search(SEARCH.START_DELTA, deltas.start);
|
||||
this.$location.search(SEARCH.END_DELTA, deltas.end);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the selected Time Conductor mode. This will call destroy
|
||||
* and initialization functions on the relevant modes, setting
|
||||
* default values for bound and deltas in the form.
|
||||
*
|
||||
* @private
|
||||
* @param newModeKey
|
||||
* @param oldModeKey
|
||||
*/
|
||||
TimeConductorController.prototype.setMode = function (newModeKey, oldModeKey) {
|
||||
//Set mode in URL on change
|
||||
this.$location.search(SEARCH.MODE, newModeKey);
|
||||
|
||||
if (newModeKey !== oldModeKey) {
|
||||
this.conductorViewService.mode(newModeKey);
|
||||
this.setFormFromMode(newModeKey);
|
||||
|
||||
if (newModeKey === "fixed") {
|
||||
this.$location.search(SEARCH.START_DELTA, null);
|
||||
this.$location.search(SEARCH.END_DELTA, null);
|
||||
} else {
|
||||
this.$location.search(SEARCH.START_BOUND, null);
|
||||
this.$location.search(SEARCH.END_BOUND, null);
|
||||
|
||||
var deltas = this.conductorViewService.deltas();
|
||||
if (deltas) {
|
||||
this.$location.search(SEARCH.START_DELTA, deltas.start);
|
||||
this.$location.search(SEARCH.END_DELTA, deltas.end);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to time system selection from UI
|
||||
*
|
||||
* Allows time system to be changed by key. This supports selection
|
||||
* from the menu. Resolves a TimeSystem object and then invokes
|
||||
* TimeConductorController#setTimeSystem
|
||||
* @param key
|
||||
* @see TimeConductorController#setTimeSystem
|
||||
*/
|
||||
TimeConductorController.prototype.selectTimeSystemByKey = function (key) {
|
||||
var selected = this.timeSystems.filter(function (timeSystem) {
|
||||
return timeSystem.metadata.key === key;
|
||||
})[0];
|
||||
this.conductor.timeSystem(selected, selected.defaults().bounds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles time system change from time conductor
|
||||
*
|
||||
* Sets the selected time system. Will populate form with the default
|
||||
* bounds and deltas defined in the selected time system.
|
||||
*
|
||||
* @param newTimeSystem
|
||||
*/
|
||||
TimeConductorController.prototype.changeTimeSystem = function (newTimeSystem) {
|
||||
//Set time system in URL on change
|
||||
this.$location.search(SEARCH.TIME_SYSTEM, newTimeSystem.metadata.key);
|
||||
|
||||
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};
|
||||
|
||||
this.setFormFromDeltas(deltas);
|
||||
this.setFormFromBounds(bounds);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.setBounds(this.$scope.boundsModel);
|
||||
this.setDeltas(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;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,543 @@
|
||||
/*****************************************************************************
|
||||
* 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(['./TimeConductorController'], function (TimeConductorController) {
|
||||
describe("The time conductor controller", function () {
|
||||
var mockScope;
|
||||
var mockWindow;
|
||||
var mockTimeConductor;
|
||||
var mockConductorViewService;
|
||||
var mockTimeSystems;
|
||||
var controller;
|
||||
var mockFormatService;
|
||||
var mockFormat;
|
||||
var mockLocation;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj("$scope", [
|
||||
"$watch",
|
||||
"$on"
|
||||
]);
|
||||
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
|
||||
mockTimeConductor = jasmine.createSpyObj(
|
||||
"TimeConductor",
|
||||
[
|
||||
"bounds",
|
||||
"timeSystem",
|
||||
"on",
|
||||
"off"
|
||||
]
|
||||
);
|
||||
mockTimeConductor.bounds.andReturn({start: undefined, end: undefined});
|
||||
|
||||
mockConductorViewService = jasmine.createSpyObj(
|
||||
"ConductorViewService",
|
||||
[
|
||||
"availableModes",
|
||||
"mode",
|
||||
"availableTimeSystems",
|
||||
"deltas",
|
||||
"deltas",
|
||||
"on",
|
||||
"off"
|
||||
]
|
||||
);
|
||||
mockConductorViewService.availableModes.andReturn([]);
|
||||
mockConductorViewService.availableTimeSystems.andReturn([]);
|
||||
|
||||
mockFormatService = jasmine.createSpyObj('formatService', [
|
||||
'getFormat'
|
||||
]);
|
||||
mockFormat = jasmine.createSpyObj('format', [
|
||||
'format'
|
||||
]);
|
||||
mockFormatService.getFormat.andReturn(mockFormat);
|
||||
mockLocation = jasmine.createSpyObj('location', [
|
||||
'search'
|
||||
]);
|
||||
mockLocation.search.andReturn({});
|
||||
|
||||
mockTimeSystems = [];
|
||||
});
|
||||
|
||||
function getListener(target, event) {
|
||||
return target.calls.filter(function (call) {
|
||||
return call.args[0] === event;
|
||||
})[0].args[1];
|
||||
}
|
||||
|
||||
describe("when time conductor state changes", function () {
|
||||
var mockDeltaFormat;
|
||||
var defaultBounds;
|
||||
var defaultDeltas;
|
||||
var mockDefaults;
|
||||
var timeSystem;
|
||||
var tsListener;
|
||||
|
||||
beforeEach(function () {
|
||||
mockFormat = {};
|
||||
mockDeltaFormat = {};
|
||||
defaultBounds = {
|
||||
start: 2,
|
||||
end: 3
|
||||
};
|
||||
defaultDeltas = {
|
||||
start: 10,
|
||||
end: 20
|
||||
};
|
||||
mockDefaults = {
|
||||
deltas: defaultDeltas,
|
||||
bounds: defaultBounds
|
||||
};
|
||||
timeSystem = {
|
||||
metadata: {
|
||||
key: 'mock'
|
||||
},
|
||||
formats: function () {
|
||||
return [mockFormat];
|
||||
},
|
||||
deltaFormat: function () {
|
||||
return mockDeltaFormat;
|
||||
},
|
||||
defaults: function () {
|
||||
return mockDefaults;
|
||||
}
|
||||
};
|
||||
|
||||
controller = new TimeConductorController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockLocation,
|
||||
{conductor: mockTimeConductor},
|
||||
mockConductorViewService,
|
||||
mockTimeSystems,
|
||||
mockFormatService
|
||||
);
|
||||
|
||||
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.changeBounds);
|
||||
});
|
||||
|
||||
it("deregisters conductor listens when scope is destroyed", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
|
||||
|
||||
controller.destroy();
|
||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
||||
});
|
||||
|
||||
it("when time system changes, sets time system on scope", function () {
|
||||
expect(tsListener).toBeDefined();
|
||||
tsListener(timeSystem);
|
||||
|
||||
expect(mockScope.timeSystemModel).toBeDefined();
|
||||
expect(mockScope.timeSystemModel.selected).toBe(timeSystem);
|
||||
expect(mockScope.timeSystemModel.format).toBe(mockFormat);
|
||||
expect(mockScope.timeSystemModel.deltaFormat).toBe(mockDeltaFormat);
|
||||
});
|
||||
|
||||
it("when time system changes, sets defaults on scope", function () {
|
||||
mockDefaults.zoom = {
|
||||
min: 100,
|
||||
max: 10
|
||||
};
|
||||
mockTimeConductor.timeSystem.andReturn(timeSystem);
|
||||
tsListener(timeSystem);
|
||||
|
||||
expect(mockScope.boundsModel.start).toEqual(defaultBounds.start);
|
||||
expect(mockScope.boundsModel.end).toEqual(defaultBounds.end);
|
||||
|
||||
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 () {
|
||||
var bounds = {
|
||||
start: 1,
|
||||
end: 2
|
||||
};
|
||||
|
||||
var boundsListener = getListener(mockTimeConductor.on, "bounds");
|
||||
expect(boundsListener).toBeDefined();
|
||||
boundsListener(bounds);
|
||||
|
||||
expect(mockScope.boundsModel).toBeDefined();
|
||||
expect(mockScope.boundsModel.start).toEqual(bounds.start);
|
||||
expect(mockScope.boundsModel.end).toEqual(bounds.end);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when user makes changes from UI", function () {
|
||||
var mode = "realtime";
|
||||
var ts1Metadata;
|
||||
var ts2Metadata;
|
||||
var ts3Metadata;
|
||||
var mockTimeSystemConstructors;
|
||||
|
||||
beforeEach(function () {
|
||||
mode = "realtime";
|
||||
ts1Metadata = {
|
||||
'key': 'ts1',
|
||||
'name': 'Time System One',
|
||||
'cssClass': 'cssClassOne'
|
||||
};
|
||||
ts2Metadata = {
|
||||
'key': 'ts2',
|
||||
'name': 'Time System Two',
|
||||
'cssClass': 'cssClassTwo'
|
||||
};
|
||||
ts3Metadata = {
|
||||
'key': 'ts3',
|
||||
'name': 'Time System Three',
|
||||
'cssClass': 'cssClassThree'
|
||||
};
|
||||
mockTimeSystems = [
|
||||
{
|
||||
metadata: ts1Metadata
|
||||
},
|
||||
{
|
||||
metadata: ts2Metadata
|
||||
},
|
||||
{
|
||||
metadata: ts3Metadata
|
||||
}
|
||||
];
|
||||
|
||||
//Wrap in mock constructors
|
||||
mockTimeSystemConstructors = mockTimeSystems.map(function (mockTimeSystem) {
|
||||
return function () {
|
||||
return mockTimeSystem;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it("sets the mode on scope", function () {
|
||||
controller = new TimeConductorController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockLocation,
|
||||
{conductor: mockTimeConductor},
|
||||
mockConductorViewService,
|
||||
mockTimeSystemConstructors,
|
||||
mockFormatService
|
||||
);
|
||||
|
||||
mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
|
||||
controller.setMode(mode);
|
||||
|
||||
expect(mockScope.modeModel.selectedKey).toEqual(mode);
|
||||
});
|
||||
|
||||
it("sets available time systems on scope when mode changes", function () {
|
||||
controller = new TimeConductorController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockLocation,
|
||||
{conductor: mockTimeConductor},
|
||||
mockConductorViewService,
|
||||
mockTimeSystemConstructors,
|
||||
mockFormatService
|
||||
);
|
||||
|
||||
mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
|
||||
controller.setMode(mode);
|
||||
|
||||
expect(mockScope.timeSystemModel.options.length).toEqual(3);
|
||||
expect(mockScope.timeSystemModel.options[0]).toEqual(ts1Metadata);
|
||||
expect(mockScope.timeSystemModel.options[1]).toEqual(ts2Metadata);
|
||||
expect(mockScope.timeSystemModel.options[2]).toEqual(ts3Metadata);
|
||||
});
|
||||
|
||||
it("sets bounds on the time conductor", function () {
|
||||
var formModel = {
|
||||
start: 1,
|
||||
end: 10
|
||||
};
|
||||
|
||||
controller = new TimeConductorController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockLocation,
|
||||
{conductor: mockTimeConductor},
|
||||
mockConductorViewService,
|
||||
mockTimeSystemConstructors,
|
||||
mockFormatService
|
||||
);
|
||||
|
||||
controller.setBounds(formModel);
|
||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel);
|
||||
});
|
||||
|
||||
it("applies deltas when they change in form", function () {
|
||||
var deltas = {
|
||||
start: 1000,
|
||||
end: 2000
|
||||
};
|
||||
var formModel = {
|
||||
startDelta: deltas.start,
|
||||
endDelta: deltas.end
|
||||
};
|
||||
|
||||
controller = new TimeConductorController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockLocation,
|
||||
{conductor: mockTimeConductor},
|
||||
mockConductorViewService,
|
||||
mockTimeSystemConstructors,
|
||||
mockFormatService
|
||||
);
|
||||
|
||||
controller.setDeltas(formModel);
|
||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas);
|
||||
});
|
||||
|
||||
it("sets the time system on the time conductor", function () {
|
||||
var defaultBounds = {
|
||||
start: 5,
|
||||
end: 6
|
||||
};
|
||||
var timeSystem = {
|
||||
metadata: {
|
||||
key: 'testTimeSystem'
|
||||
},
|
||||
defaults: function () {
|
||||
return {
|
||||
bounds: defaultBounds
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
mockTimeSystems = [
|
||||
// Wrap as constructor function
|
||||
function () {
|
||||
return timeSystem;
|
||||
}
|
||||
];
|
||||
|
||||
controller = new TimeConductorController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockLocation,
|
||||
{conductor: mockTimeConductor},
|
||||
mockConductorViewService,
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the URL defines conductor state", function () {
|
||||
var urlBounds;
|
||||
var urlTimeSystem;
|
||||
var urlMode;
|
||||
var urlDeltas;
|
||||
|
||||
var mockDeltaFormat;
|
||||
var defaultBounds;
|
||||
var defaultDeltas;
|
||||
var mockDefaults;
|
||||
var timeSystem;
|
||||
var otherTimeSystem;
|
||||
var mockSearchObject;
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
mockFormat = {};
|
||||
mockDeltaFormat = {};
|
||||
defaultBounds = {
|
||||
start: 2,
|
||||
end: 3
|
||||
};
|
||||
defaultDeltas = {
|
||||
start: 10,
|
||||
end: 20
|
||||
};
|
||||
mockDefaults = {
|
||||
deltas: defaultDeltas,
|
||||
bounds: defaultBounds
|
||||
};
|
||||
timeSystem = {
|
||||
metadata: {
|
||||
key: 'mockTimeSystem'
|
||||
},
|
||||
formats: function () {
|
||||
return [mockFormat];
|
||||
},
|
||||
deltaFormat: function () {
|
||||
return mockDeltaFormat;
|
||||
},
|
||||
defaults: function () {
|
||||
return mockDefaults;
|
||||
}
|
||||
};
|
||||
otherTimeSystem = {
|
||||
metadata: {
|
||||
key: 'otherTimeSystem'
|
||||
},
|
||||
formats: function () {
|
||||
return [mockFormat];
|
||||
},
|
||||
deltaFormat: function () {
|
||||
return mockDeltaFormat;
|
||||
},
|
||||
defaults: function () {
|
||||
return mockDefaults;
|
||||
}
|
||||
};
|
||||
|
||||
mockTimeSystems.push(function () {
|
||||
return timeSystem;
|
||||
});
|
||||
mockTimeSystems.push(function () {
|
||||
return otherTimeSystem;
|
||||
});
|
||||
|
||||
urlBounds = {
|
||||
start: 100,
|
||||
end: 200
|
||||
};
|
||||
urlTimeSystem = "otherTimeSystem";
|
||||
urlMode = "realtime";
|
||||
urlDeltas = {
|
||||
start: 300,
|
||||
end: 400
|
||||
};
|
||||
mockSearchObject = {
|
||||
"tc.startBound": urlBounds.start,
|
||||
"tc.endBound": urlBounds.end,
|
||||
"tc.startDelta": urlDeltas.start,
|
||||
"tc.endDelta": urlDeltas.end,
|
||||
"tc.timeSystem": urlTimeSystem
|
||||
};
|
||||
mockLocation.search.andReturn(mockSearchObject);
|
||||
mockTimeConductor.timeSystem.andReturn(timeSystem);
|
||||
|
||||
controller = new TimeConductorController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockLocation,
|
||||
{conductor: mockTimeConductor},
|
||||
mockConductorViewService,
|
||||
mockTimeSystems,
|
||||
mockFormatService
|
||||
);
|
||||
|
||||
spyOn(controller, "setMode");
|
||||
spyOn(controller, "selectTimeSystemByKey");
|
||||
});
|
||||
|
||||
it("sets conductor state from URL", function () {
|
||||
mockSearchObject["tc.mode"] = "fixed";
|
||||
controller.setStateFromSearchParams(mockSearchObject);
|
||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(urlBounds);
|
||||
});
|
||||
|
||||
it("sets mode from URL", function () {
|
||||
mockTimeConductor.bounds.reset();
|
||||
mockSearchObject["tc.mode"] = "realtime";
|
||||
controller.setStateFromSearchParams(mockSearchObject);
|
||||
expect(controller.setMode).toHaveBeenCalledWith("realtime");
|
||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(urlDeltas);
|
||||
expect(mockTimeConductor.bounds).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when conductor state changes", function () {
|
||||
it("updates the URL with the mode", function () {
|
||||
controller.setMode("realtime", "fixed");
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.mode", "fixed");
|
||||
});
|
||||
|
||||
it("updates the URL with the bounds", function () {
|
||||
mockConductorViewService.mode.andReturn("fixed");
|
||||
controller.changeBounds({start: 500, end: 600});
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startBound", 500);
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endBound", 600);
|
||||
});
|
||||
|
||||
it("updates the URL with the deltas", function () {
|
||||
controller.setDeltas({startDelta: 700, endDelta: 800});
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startDelta", 700);
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endDelta", 800);
|
||||
});
|
||||
|
||||
it("updates the URL with the time system", function () {
|
||||
controller.changeTimeSystem(otherTimeSystem);
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.timeSystem", "otherTimeSystem");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
248
platform/features/conductor/core/src/ui/TimeConductorMode.js
Normal file
248
platform/features/conductor/core/src/ui/TimeConductorMode.js
Normal file
@@ -0,0 +1,248 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
|
||||
/**
|
||||
* 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.deltasVal = undefined;
|
||||
this.source = undefined;
|
||||
this.sourceUnlisten = undefined;
|
||||
this.systems = timeSystems;
|
||||
this.availableSources = undefined;
|
||||
this.changeTimeSystem = this.changeTimeSystem.bind(this);
|
||||
this.tick = this.tick.bind(this);
|
||||
|
||||
//Set the time system initially
|
||||
if (conductor.timeSystem()) {
|
||||
this.changeTimeSystem(conductor.timeSystem());
|
||||
}
|
||||
|
||||
//Listen for subsequent changes to time system
|
||||
conductor.on('timeSystem', this.changeTimeSystem);
|
||||
|
||||
if (metadata.key === 'fixed') {
|
||||
//Fixed automatically supports all time systems
|
||||
this.availableSystems = timeSystems;
|
||||
} else {
|
||||
this.availableSystems = timeSystems.filter(function (timeSystem) {
|
||||
//Only include time systems that have tick sources that
|
||||
// support the current mode
|
||||
return timeSystem.tickSources().some(function (tickSource) {
|
||||
return metadata.key === tickSource.metadata.mode;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the currently selected time system
|
||||
* @param timeSystem
|
||||
* @returns {TimeSystem} the currently selected time system
|
||||
*/
|
||||
TimeConductorMode.prototype.changeTimeSystem = function (timeSystem) {
|
||||
// On time system change, apply default deltas
|
||||
var defaults = timeSystem.defaults() || {
|
||||
bounds: {
|
||||
start: 0,
|
||||
end: 0
|
||||
},
|
||||
deltas: {
|
||||
start: 0,
|
||||
end: 0
|
||||
}
|
||||
};
|
||||
|
||||
this.conductor.bounds(defaults.bounds);
|
||||
this.deltas(defaults.deltas);
|
||||
|
||||
// Tick sources are mode-specific, so restrict tick sources to only those supported by the current mode.
|
||||
var key = this.mdata.key;
|
||||
var tickSources = timeSystem.tickSources();
|
||||
if (tickSources) {
|
||||
this.availableSources = tickSources.filter(function (source) {
|
||||
return source.metadata.mode === key;
|
||||
});
|
||||
}
|
||||
|
||||
// Set an appropriate tick source from the new time system
|
||||
this.tickSource(this.availableTickSources(timeSystem)[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {ModeMetadata}
|
||||
*/
|
||||
TimeConductorMode.prototype.metadata = function () {
|
||||
return this.mdata;
|
||||
};
|
||||
|
||||
TimeConductorMode.prototype.availableTimeSystems = function () {
|
||||
return this.availableSystems;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tick sources are mode-specific. This returns a filtered list of the tick sources available in the currently selected mode
|
||||
* @param timeSystem
|
||||
* @returns {Array.<T>}
|
||||
*/
|
||||
TimeConductorMode.prototype.availableTickSources = function (timeSystem) {
|
||||
return this.availableSources;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set tick source. Setting tick source will also start
|
||||
* listening to it and unlisten from any existing tick source
|
||||
* @param tickSource
|
||||
* @returns {TickSource}
|
||||
*/
|
||||
TimeConductorMode.prototype.tickSource = function (tickSource) {
|
||||
if (arguments.length > 0) {
|
||||
if (this.sourceUnlisten) {
|
||||
this.sourceUnlisten();
|
||||
}
|
||||
this.source = tickSource;
|
||||
if (tickSource) {
|
||||
this.sourceUnlisten = tickSource.listen(this.tick);
|
||||
//Now following a tick source
|
||||
this.conductor.follow(true);
|
||||
} else {
|
||||
this.conductor.follow(false);
|
||||
}
|
||||
}
|
||||
return this.source;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TimeConductorMode.prototype.destroy = function () {
|
||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
||||
|
||||
if (this.sourceUnlisten) {
|
||||
this.sourceUnlisten();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {number} time some value that is valid in the current TimeSystem
|
||||
*/
|
||||
TimeConductorMode.prototype.tick = function (time) {
|
||||
var deltas = this.deltas();
|
||||
var startTime = time;
|
||||
var endTime = time;
|
||||
|
||||
if (deltas) {
|
||||
startTime = time - deltas.start;
|
||||
endTime = time + deltas.end;
|
||||
}
|
||||
this.conductor.bounds({
|
||||
start: startTime,
|
||||
end: endTime
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set the current value for the deltas used by this time system.
|
||||
* On change, the new deltas will be used to calculate and set the
|
||||
* bounds on the time conductor.
|
||||
* @param deltas
|
||||
* @returns {TimeSystemDeltas}
|
||||
*/
|
||||
TimeConductorMode.prototype.deltas = function (deltas) {
|
||||
if (arguments.length !== 0) {
|
||||
var bounds = this.calculateBoundsFromDeltas(deltas);
|
||||
this.deltasVal = deltas;
|
||||
if (this.metadata().key !== 'fixed') {
|
||||
this.conductor.bounds(bounds);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
);
|
||||
210
platform/features/conductor/core/src/ui/TimeConductorModeSpec.js
Normal file
210
platform/features/conductor/core/src/ui/TimeConductorModeSpec.js
Normal file
@@ -0,0 +1,210 @@
|
||||
/*****************************************************************************
|
||||
* 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(['./TimeConductorMode'], function (TimeConductorMode) {
|
||||
describe("The Time Conductor Mode", function () {
|
||||
var mockTimeConductor,
|
||||
fixedModeMetaData,
|
||||
mockTimeSystems,
|
||||
fixedTimeSystem,
|
||||
|
||||
realtimeModeMetaData,
|
||||
realtimeTimeSystem,
|
||||
mockTickSource,
|
||||
|
||||
mockBounds,
|
||||
mode;
|
||||
|
||||
beforeEach(function () {
|
||||
fixedModeMetaData = {
|
||||
key: "fixed"
|
||||
};
|
||||
realtimeModeMetaData = {
|
||||
key: "realtime"
|
||||
};
|
||||
mockBounds = {
|
||||
start: 0,
|
||||
end: 1
|
||||
};
|
||||
|
||||
fixedTimeSystem = jasmine.createSpyObj("timeSystem", [
|
||||
"defaults",
|
||||
"tickSources"
|
||||
]);
|
||||
fixedTimeSystem.tickSources.andReturn([]);
|
||||
|
||||
mockTickSource = jasmine.createSpyObj("tickSource", [
|
||||
"listen"
|
||||
]);
|
||||
mockTickSource.metadata = {
|
||||
mode: "realtime"
|
||||
};
|
||||
realtimeTimeSystem = jasmine.createSpyObj("realtimeTimeSystem", [
|
||||
"defaults",
|
||||
"tickSources"
|
||||
]);
|
||||
realtimeTimeSystem.tickSources.andReturn([mockTickSource]);
|
||||
|
||||
//Do not return any time systems initially for a default
|
||||
// construction configuration that works without any additional work
|
||||
mockTimeSystems = [];
|
||||
|
||||
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
|
||||
"bounds",
|
||||
"timeSystem",
|
||||
"on",
|
||||
"off",
|
||||
"follow"
|
||||
]);
|
||||
mockTimeConductor.bounds.andReturn(mockBounds);
|
||||
});
|
||||
|
||||
it("Reacts to changes in conductor time system", function () {
|
||||
mode = new TimeConductorMode(fixedModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", mode.changeTimeSystem);
|
||||
});
|
||||
|
||||
it("Stops listening to time system changes on destroy", function () {
|
||||
mode = new TimeConductorMode(fixedModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
mode.destroy();
|
||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", mode.changeTimeSystem);
|
||||
});
|
||||
|
||||
it("Filters available time systems to those with tick sources that" +
|
||||
" support this mode", function () {
|
||||
mockTimeSystems = [fixedTimeSystem, realtimeTimeSystem];
|
||||
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
|
||||
var availableTimeSystems = mode.availableTimeSystems();
|
||||
expect(availableTimeSystems.length).toBe(1);
|
||||
expect(availableTimeSystems.indexOf(fixedTimeSystem)).toBe(-1);
|
||||
expect(availableTimeSystems.indexOf(realtimeTimeSystem)).toBe(0);
|
||||
});
|
||||
|
||||
describe("Changing the time system", function () {
|
||||
var defaults;
|
||||
|
||||
beforeEach(function () {
|
||||
defaults = {
|
||||
bounds: {
|
||||
start: 1,
|
||||
end: 2
|
||||
},
|
||||
deltas: {
|
||||
start: 3,
|
||||
end: 4
|
||||
}
|
||||
};
|
||||
|
||||
fixedTimeSystem.defaults.andReturn(defaults);
|
||||
|
||||
});
|
||||
it ("sets defaults from new time system", function () {
|
||||
mode = new TimeConductorMode(fixedModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
spyOn(mode, "deltas");
|
||||
mode.deltas.andCallThrough();
|
||||
|
||||
mode.changeTimeSystem(fixedTimeSystem);
|
||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(defaults.bounds);
|
||||
expect(mode.deltas).toHaveBeenCalledWith(defaults.deltas);
|
||||
});
|
||||
it ("If a tick source is available, sets the tick source", function () {
|
||||
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
mode.changeTimeSystem(realtimeTimeSystem);
|
||||
|
||||
var currentTickSource = mode.tickSource();
|
||||
expect(currentTickSource).toBe(mockTickSource);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Setting a tick source", function () {
|
||||
var mockUnlistener;
|
||||
|
||||
beforeEach(function () {
|
||||
mockUnlistener = jasmine.createSpy("unlistener");
|
||||
mockTickSource.listen.andReturn(mockUnlistener);
|
||||
|
||||
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
mode.tickSource(mockTickSource);
|
||||
});
|
||||
|
||||
it ("Unlistens from old tick source", function () {
|
||||
mode.tickSource(mockTickSource);
|
||||
expect(mockUnlistener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it ("Listens to new tick source", function () {
|
||||
expect(mockTickSource.listen).toHaveBeenCalledWith(mode.tick);
|
||||
});
|
||||
|
||||
it ("Sets 'follow' state on time conductor", function () {
|
||||
expect(mockTimeConductor.follow).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it ("on destroy, unlistens from tick source", function () {
|
||||
mode.destroy();
|
||||
expect(mockUnlistener).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("setting deltas", function () {
|
||||
beforeEach(function () {
|
||||
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
});
|
||||
it ("sets the bounds on the time conductor based on new delta" +
|
||||
" values", function () {
|
||||
var deltas = {
|
||||
start: 20,
|
||||
end: 10
|
||||
};
|
||||
|
||||
mode.deltas(deltas);
|
||||
|
||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith({
|
||||
start: mockBounds.end - deltas.start,
|
||||
end: mockBounds.end + deltas.end
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ticking", function () {
|
||||
beforeEach(function () {
|
||||
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
});
|
||||
it ("sets bounds based on current delta values", function () {
|
||||
var deltas = {
|
||||
start: 20,
|
||||
end: 10
|
||||
};
|
||||
var time = 100;
|
||||
|
||||
mode.deltas(deltas);
|
||||
mode.tick(time);
|
||||
|
||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith({
|
||||
start: time - deltas.start,
|
||||
end: time + deltas.end
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Form validation for the TimeConductorController.
|
||||
* @param conductor
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductorValidation(conductor) {
|
||||
var self = this;
|
||||
this.conductor = conductor;
|
||||
|
||||
/*
|
||||
* Bind all class functions to 'this'
|
||||
*/
|
||||
Object.keys(TimeConductorValidation.prototype).filter(function (key) {
|
||||
return typeof TimeConductorValidation.prototype[key] === 'function';
|
||||
}).forEach(function (key) {
|
||||
self[key] = self[key].bind(self);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation methods below are invoked directly from controls in the TimeConductor form
|
||||
*/
|
||||
TimeConductorValidation.prototype.validateStart = function (start) {
|
||||
var bounds = this.conductor.bounds();
|
||||
return this.conductor.validateBounds({start: start, end: bounds.end}) === true;
|
||||
};
|
||||
|
||||
TimeConductorValidation.prototype.validateEnd = function (end) {
|
||||
var bounds = this.conductor.bounds();
|
||||
return this.conductor.validateBounds({start: bounds.start, end: end}) === true;
|
||||
};
|
||||
|
||||
TimeConductorValidation.prototype.validateStartDelta = function (startDelta) {
|
||||
return !isNaN(startDelta) && startDelta > 0;
|
||||
};
|
||||
|
||||
TimeConductorValidation.prototype.validateEndDelta = function (endDelta) {
|
||||
return !isNaN(endDelta) && endDelta >= 0;
|
||||
};
|
||||
|
||||
return TimeConductorValidation;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,73 @@
|
||||
/*****************************************************************************
|
||||
* 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(['./TimeConductorValidation'], function (TimeConductorValidation) {
|
||||
describe("The Time Conductor Validation class", function () {
|
||||
var timeConductorValidation,
|
||||
mockTimeConductor;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
|
||||
"validateBounds",
|
||||
"bounds"
|
||||
]);
|
||||
timeConductorValidation = new TimeConductorValidation(mockTimeConductor);
|
||||
});
|
||||
|
||||
describe("Validates start and end values using Time Conductor", function () {
|
||||
beforeEach(function () {
|
||||
var mockBounds = {
|
||||
start: 10,
|
||||
end: 20
|
||||
};
|
||||
|
||||
mockTimeConductor.bounds.andReturn(mockBounds);
|
||||
|
||||
});
|
||||
it("Validates start values using Time Conductor", function () {
|
||||
var startValue = 30;
|
||||
timeConductorValidation.validateStart(startValue);
|
||||
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
|
||||
});
|
||||
it("Validates end values using Time Conductor", function () {
|
||||
var endValue = 40;
|
||||
timeConductorValidation.validateEnd(endValue);
|
||||
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("Validates that start delta is valid number > 0", function () {
|
||||
expect(timeConductorValidation.validateStartDelta(-1)).toBe(false);
|
||||
expect(timeConductorValidation.validateStartDelta("abc")).toBe(false);
|
||||
expect(timeConductorValidation.validateStartDelta("1")).toBe(true);
|
||||
expect(timeConductorValidation.validateStartDelta(1)).toBe(true);
|
||||
});
|
||||
|
||||
it("Validates that end delta is valid number >= 0", function () {
|
||||
expect(timeConductorValidation.validateEndDelta(-1)).toBe(false);
|
||||
expect(timeConductorValidation.validateEndDelta("abc")).toBe(false);
|
||||
expect(timeConductorValidation.validateEndDelta("1")).toBe(true);
|
||||
expect(timeConductorValidation.validateEndDelta(0)).toBe(true);
|
||||
expect(timeConductorValidation.validateEndDelta(1)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,229 @@
|
||||
/*****************************************************************************
|
||||
* 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',
|
||||
'./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(openmct, timeSystems) {
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.systems = timeSystems.map(function (timeSystemConstructor) {
|
||||
return timeSystemConstructor();
|
||||
});
|
||||
|
||||
this.conductor = openmct.conductor;
|
||||
this.currentMode = undefined;
|
||||
|
||||
/**
|
||||
* @typedef {object} ModeMetadata
|
||||
* @property {string} key A unique identifying key for this mode
|
||||
* @property {string} cssClass The css class for the glyph
|
||||
* representing this mode
|
||||
* @property {string} label A short label for this mode
|
||||
* @property {string} name A longer name for the mode
|
||||
* @property {string} description A description of the mode
|
||||
*/
|
||||
this.availModes = {
|
||||
'fixed': {
|
||||
key: 'fixed',
|
||||
cssclass: 'icon-calendar',
|
||||
label: 'Fixed',
|
||||
name: 'Fixed Timespan Mode',
|
||||
description: 'Query and explore data that falls between two fixed datetimes.'
|
||||
}
|
||||
};
|
||||
|
||||
function hasTickSource(sourceType, timeSystem) {
|
||||
return timeSystem.tickSources().some(function (tickSource) {
|
||||
return tickSource.metadata.mode === sourceType;
|
||||
});
|
||||
}
|
||||
|
||||
var timeSystemsForMode = function (sourceType) {
|
||||
return this.systems.filter(hasTickSource.bind(this, sourceType));
|
||||
}.bind(this);
|
||||
|
||||
//Only show 'real-time mode' if appropriate time systems available
|
||||
if (timeSystemsForMode('realtime').length > 0) {
|
||||
var realtimeMode = {
|
||||
key: 'realtime',
|
||||
cssclass: 'icon-clock',
|
||||
label: 'Real-time',
|
||||
name: 'Real-time Mode',
|
||||
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
|
||||
};
|
||||
this.availModes[realtimeMode.key] = realtimeMode;
|
||||
}
|
||||
|
||||
//Only show 'LAD mode' if appropriate time systems available
|
||||
if (timeSystemsForMode('lad').length > 0) {
|
||||
var ladMode = {
|
||||
key: 'lad',
|
||||
cssclass: 'icon-database',
|
||||
label: 'LAD',
|
||||
name: 'LAD Mode',
|
||||
description: 'Latest Available Data mode monitors real-time streaming data as it comes in. The Time Conductor and displays will only advance when data becomes available.'
|
||||
};
|
||||
this.availModes[ladMode.key] = ladMode;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* bounds and how they change with time.
|
||||
*
|
||||
* In fixed mode, the bounds do not change with time, but can be
|
||||
* modified by the used
|
||||
*
|
||||
* In realtime mode, the bounds change with time. Bounds are not
|
||||
* directly modifiable by the user, however deltas can be.
|
||||
*
|
||||
* In Latest Available Data (LAD) mode, the bounds are updated when
|
||||
* data is received. As with realtime mode the
|
||||
*
|
||||
* @param {string} newModeKey One of 'fixed', 'realtime', or 'LAD'
|
||||
* @returns {string} the current mode, one of 'fixed', 'realtime',
|
||||
* or 'LAD'.
|
||||
*
|
||||
*/
|
||||
TimeConductorViewService.prototype.mode = function (newModeKey) {
|
||||
function contains(timeSystems, ts) {
|
||||
return timeSystems.filter(function (t) {
|
||||
return t.metadata.key === ts.metadata.key;
|
||||
}).length > 0;
|
||||
}
|
||||
|
||||
if (arguments.length === 1) {
|
||||
var timeSystem = this.conductor.timeSystem();
|
||||
var modes = this.availableModes();
|
||||
var modeMetaData = modes[newModeKey];
|
||||
|
||||
if (this.currentMode) {
|
||||
this.currentMode.destroy();
|
||||
}
|
||||
this.currentMode = new TimeConductorMode(modeMetaData, this.conductor, this.systems);
|
||||
|
||||
// If no time system set on time conductor, or the currently selected time system is not available in
|
||||
// the new mode, default to first available time system
|
||||
if (!timeSystem || !contains(this.currentMode.availableTimeSystems(), timeSystem)) {
|
||||
timeSystem = this.currentMode.availableTimeSystems()[0];
|
||||
this.conductor.timeSystem(timeSystem, timeSystem.defaults().bounds);
|
||||
}
|
||||
}
|
||||
return this.currentMode ? this.currentMode.metadata().key : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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
|
||||
* bound.
|
||||
* @property {number} end Used to set the end bound of the
|
||||
* TimeConductor on tick. A positive value that will be added
|
||||
* from the value provided by a tick source to determine the start
|
||||
* bound.
|
||||
*/
|
||||
/**
|
||||
* Deltas define the offset from the latest time value provided by
|
||||
* the current tick source. Deltas are only valid in realtime or LAD
|
||||
* modes.
|
||||
*
|
||||
* Realtime mode:
|
||||
* - start: A time in ms before now which will be used to
|
||||
* determine the 'start' bound on tick
|
||||
* - end: A time in ms after now which will be used to determine
|
||||
* the 'end' bound on tick
|
||||
*
|
||||
* LAD mode:
|
||||
* - start: A time in ms before the timestamp of the last data
|
||||
* received which will be used to determine the 'start' bound on
|
||||
* 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 {TimeConductorDeltas} current value of the deltas
|
||||
*/
|
||||
TimeConductorViewService.prototype.deltas = function () {
|
||||
//Deltas stored on mode. Use .apply to preserve arguments
|
||||
return this.currentMode.deltas.apply(this.currentMode, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Availability of modes depends on the time systems and tick
|
||||
* sources available. For example, Latest Available Data mode will
|
||||
* not be available if there are no time systems and tick sources
|
||||
* that support LAD mode.
|
||||
* @returns {ModeMetadata[]}
|
||||
*/
|
||||
TimeConductorViewService.prototype.availableModes = function () {
|
||||
return this.availModes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Availability of time systems depends on the currently selected
|
||||
* mode. Time systems and tick sources are mode dependent
|
||||
*/
|
||||
TimeConductorViewService.prototype.availableTimeSystems = function () {
|
||||
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;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,185 @@
|
||||
/*****************************************************************************
|
||||
* 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(['./TimeConductorViewService'], function (TimeConductorViewService) {
|
||||
describe("The Time Conductor view service", function () {
|
||||
var mockTimeConductor;
|
||||
var basicTimeSystem;
|
||||
var tickingTimeSystem;
|
||||
var viewService;
|
||||
var tickingTimeSystemDefaults;
|
||||
|
||||
function mockConstructor(object) {
|
||||
return function () {
|
||||
return object;
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
|
||||
"timeSystem",
|
||||
"bounds",
|
||||
"follow",
|
||||
"on",
|
||||
"off"
|
||||
]);
|
||||
|
||||
basicTimeSystem = jasmine.createSpyObj("basicTimeSystem", [
|
||||
"tickSources",
|
||||
"defaults"
|
||||
]);
|
||||
basicTimeSystem.metadata = {
|
||||
key: "basic"
|
||||
};
|
||||
basicTimeSystem.tickSources.andReturn([]);
|
||||
basicTimeSystem.defaults.andReturn({
|
||||
bounds: {
|
||||
start: 0,
|
||||
end: 1
|
||||
},
|
||||
deltas: {
|
||||
start: 0,
|
||||
end: 0
|
||||
}
|
||||
});
|
||||
//Initialize conductor
|
||||
mockTimeConductor.timeSystem.andReturn(basicTimeSystem);
|
||||
mockTimeConductor.bounds.andReturn({start: 0, end: 1});
|
||||
|
||||
tickingTimeSystem = jasmine.createSpyObj("tickingTimeSystem", [
|
||||
"tickSources",
|
||||
"defaults"
|
||||
]);
|
||||
tickingTimeSystem.metadata = {
|
||||
key: "ticking"
|
||||
};
|
||||
tickingTimeSystemDefaults = {
|
||||
bounds: {
|
||||
start: 100,
|
||||
end: 200
|
||||
},
|
||||
deltas: {
|
||||
start: 1000,
|
||||
end: 500
|
||||
}
|
||||
};
|
||||
tickingTimeSystem.defaults.andReturn(tickingTimeSystemDefaults);
|
||||
});
|
||||
|
||||
it("At a minimum supports fixed mode", function () {
|
||||
var mockTimeSystems = [mockConstructor(basicTimeSystem)];
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
|
||||
var availableModes = viewService.availableModes();
|
||||
expect(availableModes.fixed).toBeDefined();
|
||||
});
|
||||
|
||||
it("Supports realtime mode if appropriate tick source(s) availables", function () {
|
||||
var mockTimeSystems = [mockConstructor(tickingTimeSystem)];
|
||||
var mockRealtimeTickSource = {
|
||||
metadata: {
|
||||
mode: 'realtime'
|
||||
}
|
||||
};
|
||||
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
|
||||
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
|
||||
var availableModes = viewService.availableModes();
|
||||
expect(availableModes.realtime).toBeDefined();
|
||||
});
|
||||
|
||||
it("Supports LAD mode if appropriate tick source(s) available", function () {
|
||||
var mockTimeSystems = [mockConstructor(tickingTimeSystem)];
|
||||
var mockLADTickSource = {
|
||||
metadata: {
|
||||
mode: 'lad'
|
||||
}
|
||||
};
|
||||
tickingTimeSystem.tickSources.andReturn([mockLADTickSource]);
|
||||
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
|
||||
var availableModes = viewService.availableModes();
|
||||
expect(availableModes.lad).toBeDefined();
|
||||
});
|
||||
|
||||
describe("when mode is changed", function () {
|
||||
|
||||
it("destroys previous mode", function () {
|
||||
var mockTimeSystems = [mockConstructor(basicTimeSystem)];
|
||||
|
||||
var oldMode = jasmine.createSpyObj("conductorMode", [
|
||||
"destroy"
|
||||
]);
|
||||
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
viewService.currentMode = oldMode;
|
||||
viewService.mode('fixed');
|
||||
expect(oldMode.destroy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("the time system", function () {
|
||||
it("is retained if available in new mode", function () {
|
||||
var mockTimeSystems = [mockConstructor(basicTimeSystem), mockConstructor(tickingTimeSystem)];
|
||||
var mockRealtimeTickSource = {
|
||||
metadata: {
|
||||
mode: 'realtime'
|
||||
},
|
||||
listen: function () {}
|
||||
};
|
||||
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
|
||||
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
|
||||
//Set time system to one known to support realtime mode
|
||||
mockTimeConductor.timeSystem.andReturn(tickingTimeSystem);
|
||||
|
||||
//Select realtime mode
|
||||
mockTimeConductor.timeSystem.reset();
|
||||
viewService.mode('realtime');
|
||||
expect(mockTimeConductor.timeSystem).not.toHaveBeenCalledWith(tickingTimeSystem, tickingTimeSystemDefaults.bounds);
|
||||
});
|
||||
it("is defaulted if selected time system not available in new mode", function () {
|
||||
var mockTimeSystems = [mockConstructor(basicTimeSystem), mockConstructor(tickingTimeSystem)];
|
||||
var mockRealtimeTickSource = {
|
||||
metadata: {
|
||||
mode: 'realtime'
|
||||
},
|
||||
listen: function () {}
|
||||
};
|
||||
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
|
||||
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
|
||||
//Set time system to one known to not support realtime mode
|
||||
mockTimeConductor.timeSystem.andReturn(basicTimeSystem);
|
||||
|
||||
//Select realtime mode
|
||||
mockTimeConductor.timeSystem.reset();
|
||||
viewService.mode('realtime');
|
||||
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(tickingTimeSystem, tickingTimeSystemDefaults.bounds);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user