Merged from master

This commit is contained in:
Andrew Henry 2015-11-04 20:30:49 -08:00
commit b73f9fc19e
145 changed files with 14832 additions and 5432 deletions

View File

@ -1 +1 @@
web: node app.js --port $PORT --include example/localstorage
web: node app.js --port $PORT

View File

@ -103,6 +103,21 @@ This build will:
Run as `mvn clean install`.
### Building Documentation
Open MCT Web's documentation is generated by an
[npm](https://www.npmjs.com/)-based build:
* `npm install` _(only needs to run once)_
* `npm run docs`
Documentation will be generated in `target/docs`. Note that diagram
generation is dependent on having [Cairo](http://cairographics.org/download/)
installed; see
[node-canvas](https://github.com/Automattic/node-canvas#installation)'s
documentation for help with installation.
# Glossary
Certain terms are used throughout Open MCT Web with consistent meanings

View File

@ -30,7 +30,8 @@
var CONSTANTS = {
DIAGRAM_WIDTH: 800,
DIAGRAM_HEIGHT: 500
};
},
TOC_HEAD = "# Table of Contents";
GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be defined
(function () {
@ -44,6 +45,7 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define
split = require("split"),
stream = require("stream"),
nomnoml = require('nomnoml'),
toc = require("markdown-toc"),
Canvas = require('canvas'),
options = require("minimist")(process.argv.slice(2));
@ -110,6 +112,9 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define
done();
};
transform._flush = function (done) {
// Prepend table of contents
markdown =
[ TOC_HEAD, toc(markdown).content, "", markdown ].join("\n");
this.push("<html><body>\n");
this.push(marked(markdown));
this.push("\n</body></html>\n");
@ -133,8 +138,8 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define
customRenderer.link = function (href, title, text) {
// ...but only if they look like relative paths
return (href || "").indexOf(":") === -1 && href[0] !== "/" ?
renderer.link(href.replace(/\.md/, ".html"), title, text) :
renderer.link.apply(renderer, arguments);
renderer.link(href.replace(/\.md/, ".html"), title, text) :
renderer.link.apply(renderer, arguments);
};
return customRenderer;
}
@ -179,13 +184,17 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define
glob(options['in'] + "/**/*.@(html|css|png)", {}, function (err, files) {
files.forEach(function (file) {
var destination = file.replace(options['in'], options.out),
destPath = path.dirname(destination);
destPath = path.dirname(destination),
streamOptions = {};
if (file.match(/png$/)){
streamOptions.encoding = 'binary';
} else {
streamOptions.encoding = 'utf8';
}
mkdirp(destPath, function (err) {
fs.createReadStream(file, { encoding: 'utf8' })
.pipe(fs.createWriteStream(destination, {
encoding: 'utf8'
}));
fs.createReadStream(file, streamOptions)
.pipe(fs.createWriteStream(destination, streamOptions));
});
});
});

View File

@ -35,16 +35,26 @@ in __any of these tiers__.
* _DOM_: The rendered HTML document, composed from HTML templates which
have been processed by AngularJS and will be updated by AngularJS
to reflect changes from the presentation layer. User interactions
are initiated from here and invoke behavior in the presentation layer.
are initiated from here and invoke behavior in the presentation layer. HTML 
templates are written in Angulars template syntax; see the [Angular documentation on templates](https://docs.angularjs.org/guide/templates). 
These describe the page as actually seen by the user. Conceptually, 
stylesheets (controlling the look­and­feel of the rendered templates) belong 
in this grouping as well. 
* [_Presentation layer_](#presentation-layer): The presentation layer
is responsible for updating (and providing information to update)
the displayed state of the application. The presentation layer consists
primarily of _controllers_ and _directives_. The presentation layer is
concerned with inspecting the information model and preparing it for
display.
* [_Information model_](#information-model): The information model
describes the state and behavior of the objects with which the user
interacts.
* [_Information model_](#information-model): Provides a common (within Open MCT 
Web) set of interfaces for dealing with “things” ­ domain objects ­ within the 
system. User­facing concerns in a Open MCT Web application are expressed as 
domain objects; examples include folders (used to organize other domain 
objects), layouts (used to build displays), or telemetry points (used as 
handles for streams of remote measurements.) These domain objects expose a 
common set of interfaces to allow reusable user interfaces to be built in the 
presentation and template tiers; the specifics of these behaviors are then 
mapped to interactions with underlying services. 
* [_Service infrastructure_](#service-infrastructure): The service
infrastructure is responsible for providing the underlying general
functionality needed to support the information model. This includes
@ -52,7 +62,9 @@ in __any of these tiers__.
back-end.
* _Back-end_: The back-end is out of the scope of Open MCT Web, except
for the interfaces which are utilized by adapters participating in the
service infrastructure.
service infrastructure. Includes the underlying persistence stores, telemetry 
streams, and so forth which the Open MCT Web client is being used to interact 
with.
## Application Start-up

File diff suppressed because it is too large Load Diff

View File

@ -29,8 +29,9 @@
Sections:
<ul>
<li><a href="api/">API</a></li>
<li><a href="guide/">Developer Guide</a></li>
<li><a href="architecture/">Architecture Overview</a></li>
<li><a href="guide/">Developer Guide</a></li>
<li><a href="tutorials/">Tutorials</a></li>
</ul>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

3055
docs/src/tutorials/index.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,10 @@
{
"key": "time",
"name": "Time"
},
{
"key": "yesterday",
"name": "Yesterday"
}
],
"ranges": [
@ -61,4 +65,4 @@
}
]
}
}
}

View File

@ -30,25 +30,25 @@ define(
YELLOW = 0.5,
LIMITS = {
rh: {
cssClass: "s-limit-upr-red",
cssClass: "s-limit-upr s-limit-red",
low: RED,
high: Number.POSITIVE_INFINITY,
name: "Red High"
},
rl: {
cssClass: "s-limit-lwr-red",
cssClass: "s-limit-lwr s-limit-red",
high: -RED,
low: Number.NEGATIVE_INFINITY,
name: "Red Low"
},
yh: {
cssClass: "s-limit-upr-yellow",
cssClass: "s-limit-upr s-limit-yellow",
low: YELLOW,
high: RED,
name: "Yellow High"
},
yl: {
cssClass: "s-limit-lwr-yellow",
cssClass: "s-limit-lwr s-limit-yellow",
low: -RED,
high: -YELLOW,
name: "Yellow Low"

View File

@ -25,8 +25,8 @@
* Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14.
*/
define(
["./SinewaveTelemetry"],
function (SinewaveTelemetry) {
["./SinewaveTelemetrySeries"],
function (SinewaveTelemetrySeries) {
"use strict";
/**
@ -45,7 +45,7 @@ define(
function generateData(request) {
return {
key: request.key,
telemetry: new SinewaveTelemetry(request)
telemetry: new SinewaveTelemetrySeries(request)
};
}
@ -112,4 +112,4 @@ define(
return SinewaveTelemetryProvider;
}
);
);

View File

@ -29,35 +29,47 @@ define(
function () {
"use strict";
var firstObservedTime = Date.now();
var ONE_DAY = 60 * 60 * 24,
firstObservedTime = Math.floor(Date.now() / 1000) - ONE_DAY;
/**
*
* @constructor
*/
function SinewaveTelemetry(request) {
var latestObservedTime = Date.now(),
count = Math.floor((latestObservedTime - firstObservedTime) / 1000),
period = request.period || 30,
generatorData = {};
function SinewaveTelemetrySeries(request) {
var timeOffset = (request.domain === 'yesterday') ? ONE_DAY : 0,
latestTime = Math.floor(Date.now() / 1000) - timeOffset,
firstTime = firstObservedTime - timeOffset,
endTime = (request.end !== undefined) ?
Math.floor(request.end / 1000) : latestTime,
count = Math.min(endTime, latestTime) - firstTime,
period = +request.period || 30,
generatorData = {},
requestStart = (request.start === undefined) ? firstTime :
Math.max(Math.floor(request.start / 1000), firstTime),
offset = requestStart - firstTime;
if (request.size !== undefined) {
offset = Math.max(offset, count - request.size);
}
generatorData.getPointCount = function () {
return count;
return count - offset;
};
generatorData.getDomainValue = function (i, domain) {
return i * 1000 +
(domain !== 'delta' ? firstObservedTime : 0);
return (i + offset) * 1000 + firstTime * 1000 -
(domain === 'yesterday' ? ONE_DAY : 0);
};
generatorData.getRangeValue = function (i, range) {
range = range || "sin";
return Math[range](i * Math.PI * 2 / period);
return Math[range]((i + offset) * Math.PI * 2 / period);
};
return generatorData;
}
return SinewaveTelemetry;
return SinewaveTelemetrySeries;
}
);
);

View File

@ -4,7 +4,11 @@
{
"implementation": "WatchIndicator.js",
"depends": ["$interval", "$rootScope"]
},
{
"implementation": "DigestIndicator.js",
"depends": ["$interval", "$rootScope"]
}
]
}
}
}

View File

@ -0,0 +1,77 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Displays the number of digests that have occurred since the
* indicator was first instantiated.
* @constructor
* @param $interval Angular's $interval
* @implements {Indicator}
*/
function DigestIndicator($interval, $rootScope) {
var digests = 0,
displayed = 0,
start = Date.now();
function update() {
var secs = (Date.now() - start) / 1000;
displayed = Math.round(digests / secs);
}
function increment() {
digests += 1;
}
$rootScope.$watch(increment);
// Update state every second
$interval(update, 1000);
// Provide initial state, too
update();
return {
getGlyph: function () {
return ".";
},
getGlyphClass: function () {
return undefined;
},
getText: function () {
return displayed + " digests/sec";
},
getDescription: function () {
return "";
}
};
}
return DigestIndicator;
}
);

View File

@ -22,7 +22,8 @@
"split": "^1.0.0",
"mkdirp": "^0.5.1",
"nomnoml": "^0.0.3",
"canvas": "^1.2.7"
"canvas": "^1.2.7",
"markdown-toc": "^0.11.7"
},
"scripts": {
"start": "node app.js",

View File

@ -1,4 +1,9 @@
{
"configuration": {
"paths": {
"uuid": "uuid"
}
},
"extensions": {
"routes": [
{

View File

@ -42,9 +42,8 @@
</div>
</div>
<div class='object-holder abs vscroll'>
<mct-representation key="representation.selected.key"
mct-object="representation.selected.key && domainObject">
</mct-representation>
</div>
<mct-representation key="representation.selected.key"
mct-object="representation.selected.key && domainObject"
class="abs object-holder">
</mct-representation>
</span>

View File

@ -28,7 +28,9 @@
<mct-split-pane class='contents abs' anchor='left'>
<div class='split-pane-component treeview pane left'>
<div class="holder abs l-mobile">
<mct-representation key="'create-button'" mct-object="navigatedObject">
<mct-representation key="'create-button'"
mct-object="navigatedObject"
mct-device="desktop">
</mct-representation>
<div class='holder search-holder abs'
ng-class="{active: treeModel.search}">

View File

@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<div class="menu-element wrapper" ng-controller="ClickAwayController as createController">
<div class="s-menu major create-btn" ng-click="createController.toggle()">
<div class="s-menu-btn major create-btn" ng-click="createController.toggle()">
<span class="ui-symbol icon type-icon">&#x2b;</span>
<span class="title-label">Create</span>
</div>

View File

@ -25,7 +25,7 @@
* Module defining CreateService. Created by vwoeltje on 11/10/14.
*/
define(
["../../lib/uuid"],
["uuid"],
function (uuid) {
"use strict";

View File

@ -30,12 +30,11 @@
structure="toolbar.structure"
ng-model="toolbar.state">
</mct-toolbar>
<div class='holder abs object-holder work-area'>
<mct-representation key="representation.selected.key"
toolbar="toolbar"
mct-object="representation.selected.key && domainObject">
</mct-representation>
</div>
<mct-representation key="representation.selected.key"
toolbar="toolbar"
mct-object="representation.selected.key && domainObject"
class="holder abs object-holder work-area">
</mct-representation>
</div>
<mct-splitter></mct-splitter>
<div

View File

@ -8,6 +8,11 @@
"key": "urlService",
"implementation": "services/UrlService.js",
"depends": [ "$location" ]
},
{
"key": "popupService",
"implementation": "services/PopupService.js",
"depends": [ "$document", "$window" ]
}
],
"runs": [
@ -53,6 +58,16 @@
}
],
"controllers": [
{
"key": "TimeRangeController",
"implementation": "controllers/TimeRangeController.js",
"depends": [ "$scope", "now" ]
},
{
"key": "DateTimePickerController",
"implementation": "controllers/DateTimePickerController.js",
"depends": [ "$scope", "now" ]
},
{
"key": "TreeNodeController",
"implementation": "controllers/TreeNodeController.js",
@ -118,11 +133,21 @@
"implementation": "directives/MCTDrag.js",
"depends": [ "$document" ]
},
{
"key": "mctClickElsewhere",
"implementation": "directives/MCTClickElsewhere.js",
"depends": [ "$document" ]
},
{
"key": "mctResize",
"implementation": "directives/MCTResize.js",
"depends": [ "$timeout" ]
},
{
"key": "mctPopup",
"implementation": "directives/MCTPopup.js",
"depends": [ "$compile", "popupService" ]
},
{
"key": "mctScrollX",
"implementation": "directives/MCTScroll.js",
@ -226,6 +251,10 @@
{
"key": "selector",
"templateUrl": "templates/controls/selector.html"
},
{
"key": "datetime-picker",
"templateUrl": "templates/controls/datetime-picker.html"
}
],
"licenses": [

View File

@ -45,6 +45,7 @@ $ueEditToolBarH: 25px;
$ueBrowseLeftPaneW: 25%;
$ueEditLeftPaneW: 75%;
$treeSearchInputBarH: 25px;
$ueTimeControlH: (33px, 20px, 20px);
// Overlay
$ovrTopBarH: 45px;
$ovrFooterH: 24px;
@ -105,3 +106,8 @@ $dirImgs: $dirCommonRes + 'images/';
/************************** TIMINGS */
$controlFadeMs: 100ms;
/************************** LIMITS */
$glyphLimit: '\e603';
$glyphLimitUpr: '\0000eb';
$glyphLimitLwr: '\0000ee';

View File

@ -40,11 +40,11 @@
/************************** HTML ENTITIES */
a {
color: #ccc;
color: $colorA;
cursor: pointer;
text-decoration: none;
&:hover {
color: #fff;
color: $colorAHov;
}
}
@ -125,6 +125,14 @@ mct-container {
text-align: center;
}
.scrolling {
overflow: auto;
}
.vscroll {
overflow-y: auto;
}
.no-margin {
margin: 0;
}

View File

@ -29,6 +29,9 @@
}
.ui-symbol {
&.type-icon {
color: $colorObjHdrIc;
}
&.icon {
color: $colorKey;
&.alert {
@ -41,6 +44,9 @@
font-size: 1.65em;
}
}
&.icon-calendar:after {
content: "\e605";
}
}
.bar .ui-symbol {
@ -52,7 +58,7 @@
display: inline-block;
}
.s-menu .invoke-menu,
.s-menu-btn .invoke-menu,
.icon.major .invoke-menu {
margin-left: $interiorMarginSm;
}

View File

@ -1,26 +1,39 @@
@mixin limit($bg, $ic, $glyph) {
background: $bg !important;
//color: $fg !important;
&:before {
//@include pulse(1000ms);
color: $ic;
content: $glyph;
}
@mixin limitGlyph($iconColor, $glyph: $glyphLimit) {
&:before {
color: $iconColor;
content: $glyph;
font-family: symbolsfont;
font-size: 0.8em;
display: inline;
margin-right: $interiorMarginSm;
}
}
[class*="s-limit"] {
//white-space: nowrap;
&:before {
display: inline-block;
font-family: symbolsfont;
font-size: 0.75em;
font-style: normal !important;
margin-right: $interiorMarginSm;
vertical-align: middle;
}
.s-limit-red { background: $colorLimitRedBg !important; }
.s-limit-yellow { background: $colorLimitYellowBg !important; }
// Handle limit when applied to a tr
tr[class*="s-limit"] {
&.s-limit-red td:first-child {
@include limitGlyph($colorLimitRedIc);
}
&.s-limit-yellow td:first-child {
@include limitGlyph($colorLimitYellowIc);
}
&.s-limit-upr td:first-child:before { content:$glyphLimitUpr; }
&.s-limit-lwr td:first-child:before { content:$glyphLimitLwr; }
}
.s-limit-upr-red { @include limit($colorLimitRedBg, $colorLimitRedIc, "\0000eb"); };
.s-limit-upr-yellow { @include limit($colorLimitYellowBg, $colorLimitYellowIc, "\0000ed"); };
.s-limit-lwr-yellow { @include limit($colorLimitYellowBg, $colorLimitYellowIc, "\0000ec"); };
.s-limit-lwr-red { @include limit($colorLimitRedBg, $colorLimitRedIc, "\0000ee"); };
// Handle limit when applied directly to a non-tr element
// Assume this is applied to the element that displays the limit value
:not(tr)[class*="s-limit"] {
&.s-limit-red {
@include limitGlyph($colorLimitRedIc);
}
&.s-limit-yellow {
@include limitGlyph($colorLimitYellowIc);
}
&.s-limit-upr:before { content:$glyphLimitUpr; }
&.s-limit-lwr:before { content:$glyphLimitLwr; }
}

View File

@ -364,9 +364,10 @@
/* This doesn't work on an element inside an element with absolute positioning that has height: auto */
//position: relative;
top: 50%;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
@include webkitProp(transform, translateY(-50%));
//-webkit-transform: translateY(-50%);
//-ms-transform: translateY(-50%);
//transform: translateY(-50%);
}
@mixin verticalCenterBlock($holderH, $itemH) {
@ -391,22 +392,8 @@
overflow-y: $showBar;
}
@mixin wait-spinner($b: 5px, $c: $colorAlt1) {
display: block;
position: absolute;
-webkit-animation: rotation .6s infinite linear;
-moz-animation: rotation .6s infinite linear;
-o-animation: rotation .6s infinite linear;
animation: rotation .6s infinite linear;
border-color: rgba($c, 0.25);
border-top-color: rgba($c, 1.0);
border-style: solid;
border-width: $b;
border-radius: 100%;
}
@mixin test($c: #ffcc00, $a: 0.2) {
background-color: rgba($c, $a);
background-color: rgba($c, $a) !important;
}
@mixin tmpBorder($c: #ffcc00, $a: 0.75) {

View File

@ -10,9 +10,6 @@
&.fixed {
font-size: 0.8em;
}
&.scrolling {
overflow: auto;
}
.controls,
label,
.inline-block {

View File

@ -177,7 +177,7 @@ label.checkbox.custom {
}
}
.s-menu label.checkbox.custom {
.s-menu-btn label.checkbox.custom {
margin-left: 5px;
}
@ -349,49 +349,155 @@ label.checkbox.custom {
.slider {
$knobH: 100%; //14px;
$knobW: 12px;
$slotH: 50%;
.slot {
// @include border-radius($basicCr * .75);
@include sliderTrack();
height: $slotH;
//@include sliderTrack();
width: auto;
position: absolute;
top: ($knobH - $slotH) / 2;
top: 0;
right: 0;
bottom: auto;
bottom: 0;
left: 0;
}
.knob {
@include btnSubtle();
@include controlGrippy(rgba(black, 0.3), vertical, 1px, solid);
cursor: ew-resize;
//@include btnSubtle();
//@include controlGrippy(rgba(black, 0.3), vertical, 1px, solid);
@include trans-prop-nice-fade(.25s);
background-color: $sliderColorKnob;
&:hover {
background-color: $sliderColorKnobHov;
}
position: absolute;
height: $knobH;
width: $knobW;
width: $sliderKnobW;
top: 0;
auto: 0;
bottom: auto;
left: auto;
&:before {
top: 1px;
bottom: 3px;
left: ($knobW / 2) - 1;
}
}
.knob-l {
@include border-left-radius($sliderKnobW);
cursor: w-resize;
}
.knob-r {
@include border-right-radius($sliderKnobW);
cursor: e-resize;
}
.range {
background: rgba($colorKey, 0.6);
@include trans-prop-nice-fade(.25s);
background-color: $sliderColorRange;
cursor: ew-resize;
position: absolute;
top: 0;
top: 0; //$tbOffset;
right: auto;
bottom: 0;
left: auto;
height: auto;
width: auto;
&:hover {
background: rgba($colorKey, 0.7);
background-color: $sliderColorRangeHov;
}
}
}
/******************************************************** DATETIME PICKER */
.l-datetime-picker {
$r1H: 15px;
@include user-select(none);
font-size: 0.8rem;
padding: $interiorMarginLg !important;
width: 230px;
.l-month-year-pager {
$pagerW: 20px;
//@include test();
//font-size: 0.8rem;
height: $r1H;
margin-bottom: $interiorMargin;
position: relative;
.pager,
.val {
//@include test(red);
@extend .abs;
}
.pager {
width: $pagerW;
@extend .ui-symbol;
&.prev {
right: auto;
&:before {
content: "\3c";
}
}
&.next {
left: auto;
text-align: right;
&:before {
content: "\3e";
}
}
}
.val {
text-align: center;
left: $pagerW + $interiorMargin;
right: $pagerW + $interiorMargin;
}
}
.l-calendar,
.l-time-selects {
border-top: 1px solid $colorInteriorBorder
}
.l-time-selects {
line-height: $formInputH;
}
}
/******************************************************** CALENDAR */
.l-calendar {
$colorMuted: pushBack($colorMenuFg, 30%);
ul.l-cal-row {
@include display-flex;
@include flex-flow(row nowrap);
margin-top: 1px;
&:first-child {
margin-top: 0;
}
li {
@include flex(1 0);
//@include test();
margin-left: 1px;
padding: $interiorMargin;
text-align: center;
&:first-child {
margin-left: 0;
}
}
&.l-header li {
color: $colorMuted;
}
&.l-body li {
@include trans-prop-nice(background-color, .25s);
cursor: pointer;
&.in-month {
background-color: $colorCalCellInMonthBg;
}
.sub {
color: $colorMuted;
font-size: 0.8em;
}
&.selected {
background: $colorCalCellSelectedBg;
color: $colorCalCellSelectedFg;
.sub {
color: inherit;
}
}
&:hover {
background-color: $colorCalCellHovBg;
color: $colorCalCellHovFg;
.sub {
color: inherit;
}
}
}
}
}

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/******************************************************** MENU BUTTONS */
.s-menu {
.s-menu-btn {
// Formerly .btn-menu
@extend .s-btn;
span.l-click-area {
@ -62,186 +62,192 @@
/******************************************************** MENUS THEMSELVES */
.menu-element {
$bg: $colorMenuBg;
$fg: $colorMenuFg;
$ic: $colorMenuIc;
cursor: pointer;
position: relative;
.menu {
@include border-radius($basicCr);
@include containerSubtle($bg, $fg);
@include boxShdw($shdwMenu);
@include txtShdw($shdwMenuText);
display: block; // set to block via jQuery
padding: $interiorMarginSm 0;
position: absolute;
z-index: 10;
ul {
@include menuUlReset();
li {
@include box-sizing(border-box);
border-top: 1px solid lighten($bg, 20%);
color: pullForward($bg, 60%);
line-height: $menuLineH;
padding: $interiorMarginSm $interiorMargin * 2 $interiorMarginSm ($interiorMargin * 2) + $treeTypeIconW;
position: relative;
white-space: nowrap;
&:first-child {
border: none;
}
&:hover {
background: $colorMenuHovBg;
color: $colorMenuHovFg;
.icon {
color: $colorMenuHovIc;
}
}
.type-icon {
left: $interiorMargin * 2;
}
}
}
}
}
.menu,
.context-menu,
.super-menu {
pointer-events: auto;
ul li {
//padding-left: 25px;
a {
color: $fg;
}
.icon {
color: $ic;
}
.type-icon {
left: $interiorMargin;
}
&:hover .icon {
//color: lighten($ic, 5%);
}
}
}
.s-menu {
@include border-radius($basicCr);
@include containerSubtle($colorMenuBg, $colorMenuFg);
@include boxShdw($shdwMenu);
@include txtShdw($shdwMenuText);
padding: $interiorMarginSm 0;
}
.checkbox-menu {
// Used in search dropdown in tree
@extend .context-menu;
ul li {
padding-left: 50px;
.checkbox {
$d: 0.7rem;
position: absolute;
left: $interiorMargin;
top: ($menuLineH - $d) / 1.5;
em {
height: $d;
width: $d;
&:before {
font-size: 7px !important;// $d/2;
height: $d;
width: $d;
line-height: $d;
}
}
}
.type-icon {
left: 25px;
}
}
}
.super-menu {
$w: 500px;
$h: $w - 20;
$plw: 50%;
$prw: 50%;
display: block;
width: $w;
height: $h;
.contents {
@include absPosDefault($interiorMargin);
}
.pane {
.menu {
@extend .s-menu;
display: block;
position: absolute;
z-index: 10;
ul {
@include menuUlReset();
li {
@include box-sizing(border-box);
&.left {
//@include test();
border-right: 1px solid pullForward($colorMenuBg, 10%);
left: 0;
padding-right: $interiorMargin;
right: auto;
width: $plw;
overflow-x: hidden;
overflow-y: auto;
ul {
li {
@include border-radius($controlCr);
padding-left: 30px;
border-top: none;
}
border-top: 1px solid lighten($colorMenuBg, 20%);
color: pullForward($colorMenuBg, 60%);
line-height: $menuLineH;
padding: $interiorMarginSm $interiorMargin * 2 $interiorMarginSm ($interiorMargin * 2) + $treeTypeIconW;
position: relative;
white-space: nowrap;
&:first-child {
border: none;
}
&:hover {
background: $colorMenuHovBg;
color: $colorMenuHovFg;
.icon {
color: $colorMenuHovIc;
}
}
&.right {
//@include test(red);
left: auto;
right: 0;
padding: $interiorMargin * 5;
width: $prw;
.type-icon {
left: $interiorMargin * 2;
}
}
.menu-item-description {
.desc-area {
&.icon {
$h: 150px;
color: $colorCreateMenuLgIcon;
position: relative;
font-size: 8em;
left: 0;
height: $h;
line-height: $h;
margin-bottom: $interiorMargin * 5;
text-align: center;
}
&.title {
color: $colorCreateMenuText;
font-size: 1.2em;
margin-bottom: 0.5em;
}
&.description {
//color: lighten($bg, 30%);
color: $colorCreateMenuText;
font-size: 0.8em;
line-height: 1.5em;
}
}
}
}
.context-menu {
font-size: 0.80rem;
}
}
.context-menu-holder {
pointer-events: none;
.menu,
.context-menu,
.super-menu {
pointer-events: auto;
ul li {
//padding-left: 25px;
a {
color: $colorMenuFg;
}
.icon {
color: $colorMenuIc;
}
.type-icon {
left: $interiorMargin;
}
&:hover .icon {
//color: lighten($colorMenuIc, 5%);
}
}
}
.checkbox-menu {
// Used in search dropdown in tree
@extend .context-menu;
ul li {
padding-left: 50px;
.checkbox {
$d: 0.7rem;
position: absolute;
left: $interiorMargin;
top: ($menuLineH - $d) / 1.5;
em {
height: $d;
width: $d;
&:before {
font-size: 7px !important;// $d/2;
height: $d;
width: $d;
line-height: $d;
}
}
}
.type-icon {
left: 25px;
}
}
}
.super-menu {
$w: 500px;
$h: $w - 20;
$plw: 50%;
$prw: 50%;
display: block;
width: $w;
height: $h;
.contents {
@include absPosDefault($interiorMargin);
}
.pane {
@include box-sizing(border-box);
&.left {
//@include test();
border-right: 1px solid pullForward($colorMenuBg, 10%);
left: 0;
padding-right: $interiorMargin;
right: auto;
width: $plw;
overflow-x: hidden;
overflow-y: auto;
ul {
li {
@include border-radius($controlCr);
padding-left: 30px;
border-top: none;
}
}
}
&.right {
//@include test(red);
left: auto;
right: 0;
padding: $interiorMargin * 5;
width: $prw;
}
}
.menu-item-description {
.desc-area {
&.icon {
$h: 150px;
color: $colorCreateMenuLgIcon;
position: relative;
font-size: 8em;
left: 0;
height: $h;
line-height: $h;
margin-bottom: $interiorMargin * 5;
text-align: center;
}
&.title {
color: $colorCreateMenuText;
font-size: 1.2em;
margin-bottom: 0.5em;
}
&.description {
//color: lighten($colorMenuBg, 30%);
color: $colorCreateMenuText;
font-size: 0.8em;
line-height: 1.5em;
}
}
}
}
.context-menu {
font-size: 0.80rem;
}
.context-menu-holder,
.menu-holder {
position: absolute;
height: 200px;
width: 170px;
z-index: 70;
.context-menu-wrapper {
position: absolute;
height: 100%;
width: 100%;
.context-menu {
}
}
&.go-left .context-menu {
&.go-left .context-menu,
&.go-left .menu {
right: 0;
}
&.go-up .context-menu {
&.go-up .context-menu,
&.go-up .menu {
bottom: 0;
}
}
.context-menu-holder {
pointer-events: none;
height: 200px;
width: 170px;
}
.btn-bar.right .menu,
.menus-to-left .menu {
left: auto;

View File

@ -1,72 +1,155 @@
.l-time-controller {
$inputTxtW: 90px;
$knobW: 9px;
$r1H: 20px;
$r2H: 30px;
$r3H: 10px;
@mixin toiLineHovEffects() {
//@include pulse(.25s);
&:before,
&:after {
background-color: $timeControllerToiLineColorHov;
}
}
position: relative;
margin: $interiorMarginLg 0;
min-width: 400px;
.l-time-controller-visible {
}
mct-include.l-time-controller {
$minW: 500px;
$knobHOffset: 0px;
$knobM: ($sliderKnobW + $knobHOffset) * -1;
$rangeValPad: $interiorMargin;
$rangeValOffset: $sliderKnobW;
//$knobCr: $sliderKnobW;
$timeRangeSliderLROffset: 130px + $sliderKnobW + $rangeValOffset;
$r1H: nth($ueTimeControlH,1);
$r2H: nth($ueTimeControlH,2);
$r3H: nth($ueTimeControlH,3);
@include absPosDefault();
//@include test();
display: block;
top: auto;
height: $r1H + $r2H + $r3H + ($interiorMargin * 2);
min-width: $minW;
font-size: 0.8rem;
.l-time-range-inputs-holder,
.l-time-range-slider {
font-size: 0.8em;
//font-size: 0.8em;
}
.l-time-range-inputs-holder,
.l-time-range-slider-holder,
.l-time-range-ticks-holder
{
margin-bottom: $interiorMargin;
position: relative;
//@include test();
@include absPosDefault(0, visible);
@include box-sizing(border-box);
top: auto;
}
.l-time-range-slider,
.l-time-range-ticks {
//@include test(red, 0.1);
@include absPosDefault(0, visible);
left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset;
}
.l-time-range-inputs-holder {
height: $r1H;
}
.l-time-range-slider,
.l-time-range-ticks {
left: $inputTxtW; right: $inputTxtW;
//@include test(red);
height: $r1H; bottom: $r2H + $r3H + ($interiorMarginSm * 2);
padding-top: $interiorMargin;
border-top: 1px solid $colorInteriorBorder;
.type-icon {
font-size: 120%;
vertical-align: middle;
}
.l-time-range-input,
.l-time-range-inputs-elem {
margin-right: $interiorMargin;
.lbl {
color: $colorPlotLabelFg;
}
.ui-symbol.icon {
font-size: 11px;
width: 11px;
}
}
}
.l-time-range-slider-holder {
height: $r2H;
//@include test(green);
height: $r2H; bottom: $r3H + ($interiorMarginSm * 1);
.range-holder {
@include box-shadow(none);
background: none;
border: none;
height: 75%;
.range {
.toi-line {
$myC: $timeControllerToiLineColor;
$myW: 8px;
@include transform(translateX(50%));
position: absolute;
//@include test();
top: 0; right: 0; bottom: 0px; left: auto;
width: $myW;
height: auto;
z-index: 2;
&:before,
&:after {
background-color: $myC;
content: "";
position: absolute;
}
&:before {
// Vert line
top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1;
width: 2px;
//top: 0; right: 3px; bottom: 0; left: 3px;
}
&:after {
// Circle element
@include border-radius($myW);
@include transform(translateY(-50%));
top: 50%; right: 0; bottom: auto; left: 0;
width: auto;
height: $myW;
}
}
&:hover .toi-line {
@include toiLineHovEffects;
}
}
}
&:not(:active) {
//@include test(#ff00cc);
.knob,
.range {
@include transition-property(left, right);
@include transition-duration(500ms);
@include transition-timing-function(ease-in-out);
}
}
}
.l-time-range-ticks-holder {
height: $r3H;
.l-time-range-ticks {
border-top: 1px solid $colorInteriorBorder;
border-top: 1px solid $colorTick;
.tick {
background-color: $colorInteriorBorder;
background-color: $colorTick;
border:none;
height: 5px;
width: 1px;
margin-left: -1px;
position: absolute;
&:first-child {
margin-left: 0;
}
.l-time-range-tick-label {
color: lighten($colorInteriorBorder, 20%);
font-size: 0.7em;
@include webkitProp(transform, translateX(-50%));
color: $colorPlotLabelFg;
display: inline-block;
font-size: 0.9em;
position: absolute;
margin-left: -0.5 * $tickLblW;
text-align: center;
top: $r3H;
width: $tickLblW;
top: 8px;
white-space: nowrap;
z-index: 2;
}
}
@ -74,31 +157,47 @@
}
.knob {
width: $knobW;
z-index: 2;
.range-value {
$w: 75px;
//@include test();
//@include test($sliderColorRange);
@include trans-prop-nice-fade(.25s);
padding: 0 $rangeValOffset;
position: absolute;
top: 50%;
margin-top: -7px; // Label is 13px high
height: $r2H;
line-height: $r2H;
white-space: nowrap;
width: $w;
}
&:hover .range-value {
color: $colorKey;
color: $sliderColorKnobHov;
}
&.knob-l {
margin-left: $knobW / -2;
//@include border-bottom-left-radius($knobCr); // MOVED TO _CONTROLS.SCSS
margin-left: $knobM;
.range-value {
text-align: right;
right: $knobW + $interiorMargin;
right: $rangeValOffset;
}
}
&.knob-r {
margin-right: $knobW / -2;
//@include border-bottom-right-radius($knobCr);
margin-right: $knobM;
.range-value {
left: $knobW + $interiorMargin;
left: $rangeValOffset;
}
&:hover + .range-holder .range .toi-line {
@include toiLineHovEffects;
}
}
}
}
//.slot.range-holder {
// background-color: $sliderColorRangeHolder;
//}
.s-time-range-val {
//@include test();
@include border-radius($controlCr);
background-color: $colorInputBg;
padding: 1px 1px 0 $interiorMargin;
}

View File

@ -19,39 +19,44 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
@mixin complexFieldHolder($myW) {
width: $myW + $interiorMargin;
input[type="text"] {
width: $myW;
}
}
.complex.datetime {
span {
display: inline-block;
margin-right: $interiorMargin;
}
/*
.field-hints,
.fields {
}
.field-hints {
}
*/
.fields {
margin-top: $interiorMarginSm 0;
padding: $interiorMarginSm 0;
}
.date {
$myW: 80px;
width: $myW + $interiorMargin;
input {
width: $myW;
}
@include complexFieldHolder(80px);
}
.time.md {
@include complexFieldHolder(60px);
}
.time.sm {
$myW: 40px;
width: $myW + $interiorMargin;
input {
width: $myW;
}
@include complexFieldHolder(40px);
}
}

View File

@ -21,10 +21,13 @@
*****************************************************************************/
.select {
@include btnSubtle($colorSelectBg);
margin: 0 0 2px 2px; // Needed to avoid dropshadow from being clipped by parent containers
@if $shdwBtns != none {
margin: 0 0 2px 0; // Needed to avoid dropshadow from being clipped by parent containers
}
padding: 0 $interiorMargin;
overflow: hidden;
position: relative;
line-height: $formInputH;
select {
@include appearance(none);
@include box-sizing(border-box);
@ -40,11 +43,8 @@
}
&:after {
@include contextArrow();
pointer-events: none;
color: rgba($colorSelectFg, percentToDecimal($contrastInvokeMenuPercent));
//content:"v";
//display: block;
//font-family: 'symbolsfont';
//pointer-events: none;
position: absolute;
right: $interiorMargin; top: 0;
}

View File

@ -19,24 +19,45 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(359deg);}
@include keyframes(rotation) {
0% { transform: rotate(0deg); }
100% { transform: rotate(359deg); }
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(359deg);}
@mixin wait-spinner2($b: 5px, $c: $colorAlt1) {
@include keyframes(rotateCentered) {
0% { transform: translateX(-50%) translateY(-50%) rotate(0deg); }
100% { transform: translateX(-50%) translateY(-50%) rotate(359deg); }
}
@include animation-name(rotateCentered);
@include animation-duration(0.5s);
@include animation-iteration-count(infinite);
@include animation-timing-function(linear);
border-color: rgba($c, 0.25);
border-top-color: rgba($c, 1.0);
border-style: solid;
border-width: 5px;
@include border-radius(100%);
@include box-sizing(border-box);
display: block;
position: absolute;
height: 0; width: 0;
padding: 7%;
left: 50%; top: 50%;
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(359deg);}
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(359deg);}
@mixin wait-spinner($b: 5px, $c: $colorAlt1) {
display: block;
position: absolute;
-webkit-animation: rotation .6s infinite linear;
-moz-animation: rotation .6s infinite linear;
-o-animation: rotation .6s infinite linear;
animation: rotation .6s infinite linear;
border-color: rgba($c, 0.25);
border-top-color: rgba($c, 1.0);
border-style: solid;
border-width: $b;
@include border-radius(100%);
}
.t-wait-spinner,
@ -96,4 +117,28 @@
margin-top: 0 !important;
padding: 0 !important;
top: 0; left: 0;
}
.loading {
// Can be applied to any block element with height and width
pointer-events: none;
&:before,
&:after {
content: '';
}
&:before {
@include wait-spinner2(5px, $colorLoadingFg);
z-index: 10;
}
&:after {
@include absPosDefault();
background: $colorLoadingBg;
display: block;
z-index: 9;
}
&.tree-item:before {
padding: $menuLineH / 4;
border-width: 2px;
}
}

View File

@ -40,6 +40,11 @@ table {
thead, .thead {
border-bottom: 1px solid $colorTabHeaderBorder;
}
&:not(.fixed-header) tr th {
background-color: $colorTabHeaderBg;
}
tbody, .tbody {
display: table-row-group;
tr, .tr {
@ -64,7 +69,6 @@ table {
display: table-cell;
}
th, .th {
background-color: $colorTabHeaderBg;
border-left: 1px solid $colorTabHeaderBorder;
color: $colorTabHeaderFg;
padding: $tabularTdPadLR $tabularTdPadLR;
@ -143,7 +147,7 @@ table {
position: absolute;
width: 100%;
height: $tabularHeaderH;
background: rgba(#fff, 0.15);
background-color: $colorTabHeaderBg;
}
}
tbody, .tbody {

View File

@ -89,7 +89,7 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
.gl-plot-label,
.l-plot-label {
// @include test(yellow);
color: lighten($colorBodyFg, 20%);
color: $colorPlotLabelFg;
position: absolute;
text-align: center;
// text-transform: uppercase;

View File

@ -214,8 +214,6 @@
.search-scroll {
order: 3;
//padding-right: $rightPadding;
margin-top: 4px;
// Adjustable scrolling size
@ -227,28 +225,6 @@
.load-icon {
position: relative;
&.loading {
pointer-events: none;
margin-left: $leftMargin;
.title-label {
// Text styling
font-style: italic;
font-size: .9em;
opacity: 0.5;
// Text positioning
margin-left: $iconWidth + $leftMargin;
line-height: 24px;
}
.wait-spinner {
margin-left: $leftMargin;
}
}
&:not(.loading) {
cursor: pointer;
}
}
.load-more-button {

View File

@ -83,7 +83,6 @@ ul.tree {
.icon {
&.l-icon-link,
&.l-icon-alert {
//@include txtShdw($shdwItemTreeIcon);
position: absolute;
z-index: 2;
}
@ -105,26 +104,12 @@ ul.tree {
@include absPosDefault();
display: block;
left: $runningItemW + ($interiorMargin * 3);
//right: $treeContextTriggerW + $interiorMargin;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
&.loading {
pointer-events: none;
.label {
opacity: 0.5;
.title-label {
font-style: italic;
}
}
.wait-spinner {
margin-left: 14px;
}
}
&.selected {
background: $colorItemTreeSelectedBg;
color: $colorItemTreeSelectedFg;
@ -142,9 +127,6 @@ ul.tree {
&:hover {
background: rgba($colorBodyFg, 0.1); //lighten($colorBodyBg, 5%);
color: pullForward($colorBodyFg, 20%);
//.context-trigger {
// display: block;
//}
.icon {
color: $colorItemTreeIconHover;
}
@ -158,7 +140,6 @@ ul.tree {
.context-trigger {
$h: 0.9rem;
//display: none;
top: -1px;
position: absolute;
right: $interiorMarginSm;

View File

@ -47,7 +47,7 @@
}
&.frame-template {
.s-btn,
.s-menu {
.s-menu-btn {
height: $ohH;
line-height: $ohH;
padding: 0 $interiorMargin;
@ -56,7 +56,7 @@
}
}
.s-menu:after {
.s-menu-btn:after {
font-size: 8px;
}

View File

@ -30,22 +30,22 @@
}
.holder-all {
$myM: 0; // $interiorMarginSm;
top: $myM;
right: $myM;
bottom: $myM;
left: $myM;
$myM: 0; // $interiorMarginSm;
top: $myM;
right: $myM;
bottom: $myM;
left: $myM;
}
.browse-area,
.edit-area,
.editor {
position: absolute;
position: absolute;
}
//.editor {
// @include border-radius($basicCr * 1.5);
//}
.editor {
@include border-radius($basicCr * 1.5);
}
.contents {
$myM: 0; //$interiorMargin;
@ -68,8 +68,8 @@
margin-right: $interiorMargin;
}
&.abs {
text-wrap: none;
white-space: nowrap;
text-wrap: none;
white-space: nowrap;
&.left,
.left {
width: 45%;
@ -95,67 +95,51 @@
}
.user-environ {
.browse-area,
.edit-area,
.editor {
top: $bodyMargin + $ueTopBarH + ($interiorMargin);
right: $bodyMargin;
bottom: $ueFooterH + $bodyMargin;
left: $bodyMargin;
}
.browse-area,
.edit-area,
.editor {
top: $bodyMargin + $ueTopBarH + ($interiorMargin);
right: $bodyMargin;
bottom: $ueFooterH + $bodyMargin;
left: $bodyMargin;
}
.browse-area,
.edit-area {
> .contents {
left: 0;
right: 0;
}
}
.browse-area,
.edit-area {
> .contents {
left: 0;
right: 0;
}
}
.edit-area {
$tbH: $btnToolbarH + $interiorMargin;
top: $bodyMargin + $ueTopBarEditH + ($interiorMargin);
.tool-bar {
bottom: auto;
height: $tbH;
line-height: $btnToolbarH;
}
.work-area {
top: $tbH + $interiorMargin * 2;
}
}
.edit-area {
$tbH: $btnToolbarH + $interiorMargin;
top: $bodyMargin + $ueTopBarEditH + ($interiorMargin);
.tool-bar {
bottom: auto;
height: $tbH;
line-height: $btnToolbarH;
}
.work-area {
top: $tbH + $interiorMargin * 2;
}
}
// from _bottom-bar.scss
.ue-bottom-bar {
@include absPosDefault(0);// New status bar design
top: auto;
height: $ueFooterH;
line-height: $ueFooterH - ($interiorMargin * 2);
background: $colorFooterBg;
color: lighten($colorBodyBg, 30%);
font-size: .7rem;
.status-holder {
@include box-sizing(border-box);
@include absPosDefault($interiorMargin);
@include ellipsize();
//line-height: $ueFooterH - ($interiorMargin * 2);
right: 120px;
text-transform: uppercase;
z-index: 1;
}
.app-logo {
@include box-sizing(border-box);
@include absPosDefault($interiorMargin);
cursor: pointer;
left: auto;
width: $ueAppLogoW;
z-index: 2;
&.logo-openmctweb {
background: url($dirImgs + 'logo-openmctweb.svg') no-repeat center center;
}
}
}
.ue-bottom-bar {
//@include absPosDefault($bodyMargin);
@include absPosDefault(0); // New status bar design
top: auto;
height: $ueFooterH;
.status-holder {
//right: $ueAppLogoW + $bodyMargin; New status bar design
z-index: 1;
}
.app-logo {
left: auto;
width: $ueAppLogoW;
z-index: 2;
}
}
}
.cols {
@ -241,89 +225,89 @@
}
}
.pane {
position: absolute;
&.treeview.left {
.create-btn-holder {
bottom: auto; top: 0;
height: $ueTopBarH;
.wrapper.menu-element {
position: absolute;
bottom: $interiorMargin;
}
}
.search-holder {
top: $ueTopBarH + $interiorMarginLg;
}
.tree-holder {
overflow: auto;
top: $ueTopBarH + $interiorMarginLg + $treeSearchInputBarH + $interiorMargin;
}
.create-btn-holder {
bottom: auto;
top: 0;
height: $ueTopBarH;
.wrapper.menu-element {
position: absolute;
bottom: $interiorMargin;
}
}
.search-holder {
top: $ueTopBarH + $interiorMarginLg;
}
.tree-holder {
overflow: auto;
top: $ueTopBarH + $interiorMarginLg + $treeSearchInputBarH + $interiorMargin;
}
}
&.items {
.object-browse-bar {
.left.abs,
.right.abs {
top: auto;
}
//.left.abs {
// @include tmpBorder(green);
//}
//.right.abs {
// @include tmpBorder(red);
//}
}
.object-holder {
top: $ueTopBarH + $interiorMarginLg;
.left.abs,
.right.abs {
top: auto;
}
}
}
.object-holder {
overflow: auto;
}
}
.split-layout {
&.horizontal {
// Slides up and down
>.pane {
// @include test();
margin-top: $interiorMargin;
&:first-child {
margin-top: 0;
}
}
}
&.vertical {
// Slides left and right
>.pane {
// @include test();
margin-left: $interiorMargin;
>.holder {
left: 0;
right: 0;
}
&:first-child {
margin-left: 0;
.holder {
right: $interiorMarginSm;
}
}
}
&.horizontal {
// Slides up and down
> .pane {
// @include test();
margin-top: $interiorMargin;
&:first-child {
margin-top: 0;
}
}
}
&.vertical {
// Slides left and right
> .pane {
// @include test();
margin-left: $interiorMargin;
> .holder {
left: 0;
right: 0;
}
&:first-child {
margin-left: 0;
.holder {
right: $interiorMarginSm;
}
}
}
}
}
}
.object-holder {
overflow: hidden; // Contained objects need to handle their own overflow now
top: $ueTopBarH + $interiorMarginLg;
> ng-include {
@include absPosDefault(0, auto);
}
&.l-controls-visible {
&.l-time-controller-visible {
bottom: nth($ueTimeControlH,1) + nth($ueTimeControlH,2) +nth($ueTimeControlH,3) + ($interiorMargin * 3);
}
}
}
.object-browse-bar .s-btn,
.top-bar .buttons-main .s-btn,
.top-bar .s-menu,
.top-bar .s-menu-btn,
.tool-bar .s-btn,
.tool-bar .s-menu {
$h: $btnToolbarH;
height: $h;
line-height: $h;
vertical-align: top;
.tool-bar .s-menu-btn {
$h: $btnToolbarH;
height: $h;
line-height: $h;
vertical-align: top;
}
.object-browse-bar,
@ -334,33 +318,29 @@
}
.object-browse-bar {
//@include test(blue);
@include absPosDefault(0, visible);
@include box-sizing(border-box);
height: $ueTopBarH;
line-height: $ueTopBarH;
white-space: nowrap;
//@include test(blue);
@include absPosDefault(0, visible);
@include box-sizing(border-box);
height: $ueTopBarH;
line-height: $ueTopBarH;
white-space: nowrap;
.left {
padding-right: $interiorMarginLg * 2;
.l-back {
display: inline-block;
float: left;
margin-right: $interiorMarginLg;
}
}
.left {
padding-right: $interiorMarginLg * 2;
.l-back {
display: inline-block;
float: left;
margin-right: $interiorMarginLg;
}
}
}
.l-flex {
@include webkitVal('display', 'flex');
@include webkitProp('flex-flow', 'row nowrap');
.left {
//@include test(red);
@include webkitProp(flex, '1 1 0');
padding-right: $interiorMarginLg;
}
}
.vscroll {
overflow-y: auto;
}
@include webkitVal('display', 'flex');
@include webkitProp('flex-flow', 'row nowrap');
.left {
//@include test(red);
@include webkitProp(flex, '1 1 0');
padding-right: $interiorMarginLg;
}
}

View File

@ -0,0 +1,66 @@
<!--
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 ng-controller="DateTimePickerController" class="l-datetime-picker s-datetime-picker s-menu">
<div class="holder">
<div class="l-month-year-pager">
<a class="pager prev" ng-click="changeMonth(-1)"></a>
<span class="val">{{month}} {{year}}</span>
<a class="pager next" ng-click="changeMonth(1)"></a>
</div>
<div class="l-calendar">
<ul class="l-cal-row l-header">
<li ng-repeat="day in ['Su','Mo','Tu','We','Th','Fr','Sa']">{{day}}</li>
</ul>
<ul class="l-cal-row l-body" ng-repeat="row in table">
<li ng-repeat="cell in row"
ng-click="select(cell)"
ng-class='{ "in-month": isInCurrentMonth(cell), selected: isSelected(cell) }'>
<div class="prime">{{cell.day}}</div>
<div class="sub">{{cell.dayOfYear}}</div>
</li>
</ul>
</div>
</div>
<div class="l-time-selects complex datetime"
ng-show="options">
<div class="field-hints">
<span class="hint time md"
ng-repeat="key in ['hours', 'minutes', 'seconds']"
ng-if="options[key]">
{{nameFor(key)}}
</span>
</div>
<div>
<span class="field control time md"
ng-repeat="key in ['hours', 'minutes', 'seconds']"
ng-if="options[key]">
<div class='form-control select'>
<select size="1"
ng-model="time[key]"
ng-options="i for i in optionsFor(key)">
</select>
</div>
</span>
</div>
</div>
</div>

View File

@ -21,7 +21,7 @@
-->
<span ng-controller="ViewSwitcherController">
<div
class="view-switcher menu-element s-menu"
class="view-switcher menu-element s-menu-btn"
ng-if="view.length > 1"
ng-controller="ClickAwayController as toggle"
>

View File

@ -1,69 +1,108 @@
<!--
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.
NOTES
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.
Ticks:
The thinking is to divide whatever the current time span is by 5,
and assign values accordingly to 5 statically-positioned ticks. So the tick x-position is a static percentage
of the total width available, and the labels change dynamically. This is consistent
with our current approach to the time axis of plots.
I'm keeping the number of ticks low so that when the view portal gets narrow,
the tick labels won't collide with each other. For extra credit, add/remove ticks as the user resizes the view area.
Note: this eval needs to be based on the whatever is containing the
time-controller component, not the whole browser window.
Range indicator and slider knobs:
The left and right properties used in .slider .range-holder and the .knobs are
CSS offsets from the left and right of their respective containers. You
may want or need to calculate those positions as pure offsets from the start datetime
(or left, as it were) and set them as left properties. No problem if so, but
we'll need to tweak the CSS tiny bit to get the center of the knobs to line up
properly on the range left and right bounds.
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 ng-controller="TimeRangeController">
<div class="l-time-range-inputs-holder">
<span class="l-time-range-inputs-elem ui-symbol type-icon">&#x43;</span>
<span class="l-time-range-input" ng-controller="ToggleController as t1">
<!--<span class="lbl">Start</span>-->
<span class="s-btn time-range-start">
<input type="text"
ng-model="boundsModel.start"
ng-class="{ error: !boundsModel.startValid }">
</input>
<a class="ui-symbol icon icon-calendar" ng-click="t1.toggle()"></a>
<mct-popup ng-if="t1.isActive()">
<div mct-click-elsewhere="t1.setState(false)">
<mct-control key="'datetime-picker'"
ng-model="ngModel.outer"
field="'start'"
options="{ hours: true }">
</mct-control>
</div>
</mct-popup>
</span>
</span>
<div ng-init="
notes = 'Temporarily using an array to populate ticks so I can see what I\'m doing';
ticks = [
'00:00',
'00:30',
'01:00',
'01:30',
'02:00'
];
"></div>
<span class="l-time-range-inputs-elem lbl">to</span>
<div class="l-time-controller">
<div class="l-time-range-inputs-holder">
Start: <input type="date" />
End: <input type="date" />
</div>
<span class="l-time-range-input" ng-controller="ToggleController as t2">
<!--<span class="lbl">End</span>-->
<span class="s-btn l-time-range-input">
<input type="text"
ng-model="boundsModel.end"
ng-class="{ error: !boundsModel.endValid }">
</input>
<a class="ui-symbol icon icon-calendar" ng-click="t2.toggle()">
</a>
<mct-popup ng-if="t2.isActive()">
<div mct-click-elsewhere="t2.setState(false)">
<mct-control key="'datetime-picker'"
ng-model="ngModel.outer"
field="'end'"
options="{ hours: true }">
</mct-control>
</div>
</mct-popup>
</span>&nbsp;
</span>
</div>
<div class="l-time-range-slider-holder">
<div class="l-time-range-slider">
<div class="slider">
<div class="slot range-holder">
<div class="range" style="left: 0%; right: 30%;"></div>
</div>
<div class="knob knob-l" style="left: 0%;">
<div class="range-value">05/22 14:46</div>
</div>
<div class="knob knob-r" style="right: 30%;">
<div class="range-value">07/22 01:21</div>
</div>
</div>
</div>
</div>
<div class="l-time-range-slider-holder">
<div class="l-time-range-slider">
<div class="slider"
mct-resize="spanWidth = bounds.width">
<div class="knob knob-l"
mct-drag-down="startLeftDrag()"
mct-drag="leftDrag(delta[0])"
ng-style="{ left: startInnerPct }">
<div class="range-value">{{startInnerText}}</div>
</div>
<div class="knob knob-r"
mct-drag-down="startRightDrag()"
mct-drag="rightDrag(delta[0])"
ng-style="{ right: endInnerPct }">
<div class="range-value">{{endInnerText}}</div>
</div>
<div class="slot range-holder">
<div class="range"
mct-drag-down="startMiddleDrag()"
mct-drag="middleDrag(delta[0])"
ng-style="{ left: startInnerPct, right: endInnerPct}">
<div class="toi-line"></div>
</div>
</div>
</div>
</div>
</div>
<div class="l-time-range-ticks-holder">
<div class="l-time-range-ticks">
<div
ng-repeat="tick in ticks"
ng-style="{ left: $index * 25 + '%' }"
class="tick tick-x"
>
<span class="l-time-range-tick-label">{{tick}}</span>
</div>
</div>
</div>
</div>
<div class="l-time-range-ticks-holder">
<div class="l-time-range-ticks">
<div
ng-repeat="tick in ticks"
ng-style="{ left: $index * (100 / (ticks.length - 1)) + '%' }"
class="tick tick-x"
>
<span class="l-time-range-tick-label">{{tick}}</span>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,202 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise*/
define(
[ 'moment' ],
function (moment) {
'use strict';
var TIME_NAMES = {
'hours': "Hour",
'minutes': "Minute",
'seconds': "Second"
},
MONTHS = moment.months(),
TIME_OPTIONS = (function makeRanges() {
var arr = [];
while (arr.length < 60) {
arr.push(arr.length);
}
return {
hours: arr.slice(0, 24),
minutes: arr,
seconds: arr
};
}());
/**
* Controller to support the date-time picker.
*
* Adds/uses the following properties in scope:
* * `year`: Year being displayed in picker
* * `month`: Month being displayed
* * `table`: Table being displayed; array of arrays of
* * `day`: Day of month
* * `dayOfYear`: Day of year
* * `month`: Month associated with the day
* * `year`: Year associated with the day.
* * `date`: Date chosen
* * `year`: Year selected
* * `month`: Month selected (0-indexed)
* * `day`: Day of month selected
* * `time`: Chosen time (hours/minutes/seconds)
* * `hours`: Hours chosen
* * `minutes`: Minutes chosen
* * `seconds`: Seconds chosen
*
* Months are zero-indexed, day-of-months are one-indexed.
*/
function DateTimePickerController($scope, now) {
var year,
month, // For picker state, not model state
interacted = false;
function generateTable() {
var m = moment.utc({ year: year, month: month }).day(0),
table = [],
row,
col;
for (row = 0; row < 6; row += 1) {
table.push([]);
for (col = 0; col < 7; col += 1) {
table[row].push({
year: m.year(),
month: m.month(),
day: m.date(),
dayOfYear: m.dayOfYear()
});
m.add(1, 'days'); // Next day!
}
}
return table;
}
function updateScopeForMonth() {
$scope.month = MONTHS[month];
$scope.year = year;
$scope.table = generateTable();
}
function updateFromModel(ngModel) {
var m;
m = moment.utc(ngModel);
$scope.date = {
year: m.year(),
month: m.month(),
day: m.date()
};
$scope.time = {
hours: m.hour(),
minutes: m.minute(),
seconds: m.second()
};
//window.alert($scope.date.day + " " + ngModel);
// Zoom to that date in the picker, but
// only if the user hasn't interacted with it yet.
if (!interacted) {
year = m.year();
month = m.month();
updateScopeForMonth();
}
}
function updateFromView() {
var m = moment.utc({
year: $scope.date.year,
month: $scope.date.month,
day: $scope.date.day,
hour: $scope.time.hours,
minute: $scope.time.minutes,
second: $scope.time.seconds
});
$scope.ngModel[$scope.field] = m.valueOf();
}
$scope.isInCurrentMonth = function (cell) {
return cell.month === month;
};
$scope.isSelected = function (cell) {
var date = $scope.date || {};
return cell.day === date.day &&
cell.month === date.month &&
cell.year === date.year;
};
$scope.select = function (cell) {
$scope.date = $scope.date || {};
$scope.date.month = cell.month;
$scope.date.year = cell.year;
$scope.date.day = cell.day;
updateFromView();
};
$scope.dateEquals = function (d1, d2) {
return d1.year === d2.year &&
d1.month === d2.month &&
d1.day === d2.day;
};
$scope.changeMonth = function (delta) {
month += delta;
if (month > 11) {
month = 0;
year += 1;
}
if (month < 0) {
month = 11;
year -= 1;
}
interacted = true;
updateScopeForMonth();
};
$scope.nameFor = function (key) {
return TIME_NAMES[key];
};
$scope.optionsFor = function (key) {
return TIME_OPTIONS[key];
};
updateScopeForMonth();
// Ensure some useful default
$scope.ngModel[$scope.field] =
$scope.ngModel[$scope.field] === undefined ?
now() : $scope.ngModel[$scope.field];
$scope.$watch('ngModel[field]', updateFromModel);
$scope.$watchCollection('date', updateFromView);
$scope.$watchCollection('time', updateFromView);
}
return DateTimePickerController;
}
);

View File

@ -0,0 +1,302 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise*/
define(
['moment'],
function (moment) {
"use strict";
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss",
TICK_SPACING_PX = 150;
/**
* @memberof platform/commonUI/general
* @constructor
*/
function TimeConductorController($scope, now) {
var tickCount = 2,
innerMinimumSpan = 1000, // 1 second
outerMinimumSpan = 1000 * 60 * 60, // 1 hour
initialDragValue;
function formatTimestamp(ts) {
return moment.utc(ts).format(DATE_FORMAT);
}
function parseTimestamp(text) {
var m = moment.utc(text, DATE_FORMAT);
if (m.isValid()) {
return m.valueOf();
} else {
throw new Error("Could not parse " + text);
}
}
// From 0.0-1.0 to "0%"-"1%"
function toPercent(p) {
return (100 * p) + "%";
}
function updateTicks() {
var i, p, ts, start, end, span;
end = $scope.ngModel.outer.end;
start = $scope.ngModel.outer.start;
span = end - start;
$scope.ticks = [];
for (i = 0; i < tickCount; i += 1) {
p = i / (tickCount - 1);
ts = p * span + start;
$scope.ticks.push(formatTimestamp(ts));
}
}
function updateSpanWidth(w) {
tickCount = Math.max(Math.floor(w / TICK_SPACING_PX), 2);
updateTicks();
}
function updateViewForInnerSpanFromModel(ngModel) {
var span = ngModel.outer.end - ngModel.outer.start;
// Expose readable dates for the knobs
$scope.startInnerText = formatTimestamp(ngModel.inner.start);
$scope.endInnerText = formatTimestamp(ngModel.inner.end);
// And positions for the knobs
$scope.startInnerPct =
toPercent((ngModel.inner.start - ngModel.outer.start) / span);
$scope.endInnerPct =
toPercent((ngModel.outer.end - ngModel.inner.end) / span);
}
function defaultBounds() {
var t = now();
return {
start: t - 24 * 3600 * 1000, // One day
end: t
};
}
function copyBounds(bounds) {
return { start: bounds.start, end: bounds.end };
}
function updateBoundsTextForProperty(ngModel, property) {
try {
if (!$scope.boundsModel[property] ||
parseTimestamp($scope.boundsModel[property]) !==
ngModel.outer[property]) {
$scope.boundsModel[property] =
formatTimestamp(ngModel.outer[property]);
}
} catch (e) {
// User-entered text is invalid, so leave it be
// until they fix it.
}
}
function updateBoundsText(ngModel) {
updateBoundsTextForProperty(ngModel, 'start');
updateBoundsTextForProperty(ngModel, 'end');
}
function updateViewFromModel(ngModel) {
var t = now();
ngModel = ngModel || {};
ngModel.outer = ngModel.outer || defaultBounds();
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
// First, dates for the date pickers for outer bounds
updateBoundsText(ngModel);
// Then various updates for the inner span
updateViewForInnerSpanFromModel(ngModel);
// Stick it back is scope (in case we just set defaults)
$scope.ngModel = ngModel;
updateTicks();
}
function startLeftDrag() {
initialDragValue = $scope.ngModel.inner.start;
}
function startRightDrag() {
initialDragValue = $scope.ngModel.inner.end;
}
function startMiddleDrag() {
initialDragValue = {
start: $scope.ngModel.inner.start,
end: $scope.ngModel.inner.end
};
}
function toMillis(pixels) {
var span = $scope.ngModel.outer.end - $scope.ngModel.outer.start;
return (pixels / $scope.spanWidth) * span;
}
function clamp(value, low, high) {
return Math.max(low, Math.min(high, value));
}
function leftDrag(pixels) {
var delta = toMillis(pixels);
$scope.ngModel.inner.start = clamp(
initialDragValue + delta,
$scope.ngModel.outer.start,
$scope.ngModel.inner.end - innerMinimumSpan
);
updateViewFromModel($scope.ngModel);
}
function rightDrag(pixels) {
var delta = toMillis(pixels);
$scope.ngModel.inner.end = clamp(
initialDragValue + delta,
$scope.ngModel.inner.start + innerMinimumSpan,
$scope.ngModel.outer.end
);
updateViewFromModel($scope.ngModel);
}
function middleDrag(pixels) {
var delta = toMillis(pixels),
edge = delta < 0 ? 'start' : 'end',
opposite = delta < 0 ? 'end' : 'start';
// Adjust the position of the edge in the direction of drag
$scope.ngModel.inner[edge] = clamp(
initialDragValue[edge] + delta,
$scope.ngModel.outer.start,
$scope.ngModel.outer.end
);
// Adjust opposite knob to maintain span
$scope.ngModel.inner[opposite] = $scope.ngModel.inner[edge] +
initialDragValue[opposite] - initialDragValue[edge];
updateViewFromModel($scope.ngModel);
}
function updateOuterStart(t) {
var ngModel = $scope.ngModel;
ngModel.outer.start = t;
ngModel.outer.end = Math.max(
ngModel.outer.start + outerMinimumSpan,
ngModel.outer.end
);
ngModel.inner.start =
Math.max(ngModel.outer.start, ngModel.inner.start);
ngModel.inner.end = Math.max(
ngModel.inner.start + innerMinimumSpan,
ngModel.inner.end
);
updateViewForInnerSpanFromModel(ngModel);
updateTicks();
}
function updateOuterEnd(t) {
var ngModel = $scope.ngModel;
ngModel.outer.end = t;
ngModel.outer.start = Math.min(
ngModel.outer.end - outerMinimumSpan,
ngModel.outer.start
);
ngModel.inner.end =
Math.min(ngModel.outer.end, ngModel.inner.end);
ngModel.inner.start = Math.min(
ngModel.inner.end - innerMinimumSpan,
ngModel.inner.start
);
updateViewForInnerSpanFromModel(ngModel);
updateTicks();
}
function updateStartFromText(value) {
try {
updateOuterStart(parseTimestamp(value));
updateBoundsTextForProperty($scope.ngModel, 'end');
$scope.boundsModel.startValid = true;
} catch (e) {
$scope.boundsModel.startValid = false;
return;
}
}
function updateEndFromText(value) {
try {
updateOuterEnd(parseTimestamp(value));
updateBoundsTextForProperty($scope.ngModel, 'start');
$scope.boundsModel.endValid = true;
} catch (e) {
$scope.boundsModel.endValid = false;
return;
}
}
function updateStartFromPicker(value) {
updateOuterStart(value);
updateBoundsText($scope.ngModel);
}
function updateEndFromPicker(value) {
updateOuterEnd(value);
updateBoundsText($scope.ngModel);
}
$scope.startLeftDrag = startLeftDrag;
$scope.startRightDrag = startRightDrag;
$scope.startMiddleDrag = startMiddleDrag;
$scope.leftDrag = leftDrag;
$scope.rightDrag = rightDrag;
$scope.middleDrag = middleDrag;
$scope.state = false;
$scope.ticks = [];
$scope.boundsModel = {};
// Initialize scope to defaults
updateViewFromModel($scope.ngModel);
$scope.$watchCollection("ngModel", updateViewFromModel);
$scope.$watch("spanWidth", updateSpanWidth);
$scope.$watch("ngModel.outer.start", updateStartFromPicker);
$scope.$watch("ngModel.outer.end", updateEndFromPicker);
$scope.$watch("boundsModel.start", updateStartFromText);
$scope.$watch("boundsModel.end", updateEndFromText);
}
return TimeConductorController;
}
);

View File

@ -0,0 +1,77 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* The `mct-click-elsewhere` directive will evaluate its
* associated expression whenever a `mousedown` occurs anywhere
* outside of the element that has the `mct-click-elsewhere`
* directive attached. This is useful for dismissing popups
* and the like.
*/
function MCTClickElsewhere($document) {
// Link; install event handlers.
function link(scope, element, attrs) {
// Keep a reference to the body, to attach/detach
// mouse event handlers; mousedown and mouseup cannot
// only be attached to the element being linked, as the
// mouse may leave this element during the drag.
var body = $document.find('body');
function clickBody(event) {
var x = event.clientX,
y = event.clientY,
rect = element[0].getBoundingClientRect(),
xMin = rect.left,
xMax = xMin + rect.width,
yMin = rect.top,
yMax = yMin + rect.height;
if (x < xMin || x > xMax || y < yMin || y > yMax) {
scope.$eval(attrs.mctClickElsewhere);
}
}
body.on("mousedown", clickBody);
scope.$on("$destroy", function () {
body.off("mousedown", clickBody);
});
}
return {
// mct-drag only makes sense as an attribute
restrict: "A",
// Link function, to install event handlers
link: link
};
}
return MCTClickElsewhere;
}
);

View File

@ -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.
*****************************************************************************/
/*global define*/
define(
function () {
'use strict';
var TEMPLATE = "<div></div>";
/**
* The `mct-popup` directive may be used to display elements
* which "pop up" over other parts of the page. Typically, this is
* done in conjunction with an `ng-if` to control the visibility
* of the popup.
*
* Example of usage:
*
* <mct-popup ng-if="someExpr">
* <span>These are the contents of the popup!</span>
* </mct-popup>
*
* @constructor
* @memberof platform/commonUI/general
* @param $compile Angular's $compile service
* @param {platform/commonUI/general.PopupService} popupService
*/
function MCTPopup($compile, popupService) {
function link(scope, element, attrs, ctrl, transclude) {
var div = $compile(TEMPLATE)(scope),
rect = element.parent()[0].getBoundingClientRect(),
position = [ rect.left, rect.top ],
popup = popupService.display(div, position);
transclude(function (clone) {
div.append(clone);
});
scope.$on('$destroy', function () {
popup.dismiss();
});
}
return {
restrict: "E",
transclude: true,
link: link,
scope: {}
};
}
return MCTPopup;
}
);

View File

@ -58,6 +58,7 @@ define(
// Link; start listening for changes to an element's size
function link(scope, element, attrs) {
var lastBounds,
linking = true,
active = true;
// Determine how long to wait before the next update
@ -74,7 +75,9 @@ define(
lastBounds.width !== bounds.width ||
lastBounds.height !== bounds.height) {
scope.$eval(attrs.mctResize, { bounds: bounds });
scope.$apply(); // Trigger a digest
if (!linking) { // Avoid apply-in-a-digest
scope.$apply();
}
lastBounds = bounds;
}
}
@ -101,6 +104,9 @@ define(
// Handle the initial callback
onInterval();
// Trigger scope.$apply on subsequent changes
linking = false;
}
return {

View File

@ -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.
*****************************************************************************/
/*global define*/
define(
function () {
"use strict";
/**
* A popup is an element that has been displayed at a particular
* location within the page.
* @constructor
* @memberof platform/commonUI/general
* @param element the jqLite-wrapped element
* @param {object} styles an object containing key-value pairs
* of styles used to position the element.
*/
function Popup(element, styles) {
this.styles = styles;
this.element = element;
element.css(styles);
}
/**
* Stop showing this popup.
*/
Popup.prototype.dismiss = function () {
this.element.remove();
};
/**
* Check if this popup is positioned such that it appears to the
* left of its original location.
* @returns {boolean} true if the popup goes left
*/
Popup.prototype.goesLeft = function () {
return !this.styles.left;
};
/**
* Check if this popup is positioned such that it appears to the
* right of its original location.
* @returns {boolean} true if the popup goes right
*/
Popup.prototype.goesRight = function () {
return !this.styles.right;
};
/**
* Check if this popup is positioned such that it appears above
* its original location.
* @returns {boolean} true if the popup goes up
*/
Popup.prototype.goesUp = function () {
return !this.styles.top;
};
/**
* Check if this popup is positioned such that it appears below
* its original location.
* @returns {boolean} true if the popup goes down
*/
Popup.prototype.goesDown = function () {
return !this.styles.bottom;
};
return Popup;
}
);

View File

@ -0,0 +1,127 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./Popup'],
function (Popup) {
"use strict";
/**
* Displays popup elements at specific positions within the document.
* @memberof platform/commonUI/general
* @constructor
*/
function PopupService($document, $window) {
this.$document = $document;
this.$window = $window;
}
/**
* Options controlling how the popup is displaed.
*
* @typedef PopupOptions
* @memberof platform/commonUI/general
* @property {number} [offsetX] the horizontal distance, in pixels,
* to offset the element in whichever direction it is
* displayed. Defaults to 0.
* @property {number} [offsetY] the vertical distance, in pixels,
* to offset the element in whichever direction it is
* displayed. Defaults to 0.
* @property {number} [marginX] the horizontal position, in pixels,
* after which to prefer to display the element to the left.
* If negative, this is relative to the right edge of the
* page. Defaults to half the window's width.
* @property {number} [marginY] the vertical position, in pixels,
* after which to prefer to display the element upward.
* If negative, this is relative to the right edge of the
* page. Defaults to half the window's height.
* @property {string} [leftClass] class to apply when shifting to the left
* @property {string} [rightClass] class to apply when shifting to the right
* @property {string} [upClass] class to apply when shifting upward
* @property {string} [downClass] class to apply when shifting downward
*/
/**
* Display a popup at a particular location. The location chosen will
* be the corner of the element; the element will be positioned either
* to the left or the right of this point depending on available
* horizontal space, and will similarly be shifted upward or downward
* depending on available vertical space.
*
* @param element the jqLite-wrapped DOM element to pop up
* @param {number[]} position x,y position of the element, in
* pixel coordinates. Negative values are interpreted as
* relative to the right or bottom of the window.
* @param {PopupOptions} [options] additional options to control
* positioning of the popup
* @returns {platform/commonUI/general.Popup} the popup
*/
PopupService.prototype.display = function (element, position, options) {
var $document = this.$document,
$window = this.$window,
body = $document.find('body'),
winDim = [ $window.innerWidth, $window.innerHeight ],
styles = { position: 'absolute' },
margin,
offset,
bubble;
function adjustNegatives(value, index) {
return value < 0 ? (value + winDim[index]) : value;
}
// Defaults
options = options || {};
offset = [
options.offsetX !== undefined ? options.offsetX : 0,
options.offsetY !== undefined ? options.offsetY : 0
];
margin = [ options.marginX, options.marginY ].map(function (m, i) {
return m === undefined ? (winDim[i] / 2) : m;
}).map(adjustNegatives);
position = position.map(adjustNegatives);
if (position[0] > margin[0]) {
styles.right = (winDim[0] - position[0] + offset[0]) + 'px';
} else {
styles.left = (position[0] + offset[0]) + 'px';
}
if (position[1] > margin[1]) {
styles.bottom = (winDim[1] - position[1] + offset[1]) + 'px';
} else {
styles.top = (position[1] + offset[1]) + 'px';
}
// Add the menu to the body
body.append(element);
// Return a function to dismiss the bubble
return new Popup(element, styles);
};
return PopupService;
}
);

View File

@ -0,0 +1,63 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../../src/controllers/DateTimePickerController"],
function (DateTimePickerController) {
"use strict";
describe("The DateTimePickerController", function () {
var mockScope,
mockNow,
controller;
function fireWatch(expr, value) {
mockScope.$watch.calls.forEach(function (call) {
if (call.args[0] === expr) {
call.args[1](value);
}
});
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
[ "$apply", "$watch", "$watchCollection" ]
);
mockScope.ngModel = {};
mockScope.field = "testField";
mockNow = jasmine.createSpy('now');
controller = new DateTimePickerController(mockScope, mockNow);
});
it("watches the model that was passed in", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"ngModel[field]",
jasmine.any(Function)
);
});
});
}
);

View File

@ -0,0 +1,237 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../../src/controllers/TimeRangeController", "moment"],
function (TimeRangeController, moment) {
"use strict";
var SEC = 1000,
MIN = 60 * SEC,
HOUR = 60 * MIN,
DAY = 24 * HOUR;
describe("The TimeRangeController", function () {
var mockScope,
mockNow,
controller;
function fireWatch(expr, value) {
mockScope.$watch.calls.forEach(function (call) {
if (call.args[0] === expr) {
call.args[1](value);
}
});
}
function fireWatchCollection(expr, value) {
mockScope.$watchCollection.calls.forEach(function (call) {
if (call.args[0] === expr) {
call.args[1](value);
}
});
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
[ "$apply", "$watch", "$watchCollection" ]
);
mockNow = jasmine.createSpy('now');
controller = new TimeRangeController(mockScope, mockNow);
});
it("watches the model that was passed in", function () {
expect(mockScope.$watchCollection)
.toHaveBeenCalledWith("ngModel", jasmine.any(Function));
});
describe("when dragged", function () {
beforeEach(function () {
mockScope.ngModel = {
outer: {
start: DAY * 1000,
end: DAY * 1001
},
inner: {
start: DAY * 1000 + HOUR * 3,
end: DAY * 1001 - HOUR * 3
}
};
mockScope.spanWidth = 1000;
fireWatch("spanWidth", mockScope.spanWidth);
fireWatchCollection("ngModel", mockScope.ngModel);
});
it("updates the start time for left drags", function () {
mockScope.startLeftDrag();
mockScope.leftDrag(250);
expect(mockScope.ngModel.inner.start)
.toEqual(DAY * 1000 + HOUR * 9);
});
it("updates the end time for right drags", function () {
mockScope.startRightDrag();
mockScope.rightDrag(-250);
expect(mockScope.ngModel.inner.end)
.toEqual(DAY * 1000 + HOUR * 15);
});
it("updates both start and end for middle drags", function () {
mockScope.startMiddleDrag();
mockScope.middleDrag(-125);
expect(mockScope.ngModel.inner).toEqual({
start: DAY * 1000,
end: DAY * 1000 + HOUR * 18
});
mockScope.middleDrag(250);
expect(mockScope.ngModel.inner).toEqual({
start: DAY * 1000 + HOUR * 6,
end: DAY * 1001
});
});
it("enforces a minimum inner span", function () {
mockScope.startRightDrag();
mockScope.rightDrag(-9999999);
expect(mockScope.ngModel.inner.end)
.toBeGreaterThan(mockScope.ngModel.inner.start);
});
});
describe("when outer bounds are changed", function () {
beforeEach(function () {
mockScope.ngModel = {
outer: {
start: DAY * 1000,
end: DAY * 1001
},
inner: {
start: DAY * 1000 + HOUR * 3,
end: DAY * 1001 - HOUR * 3
}
};
mockScope.spanWidth = 1000;
fireWatch("spanWidth", mockScope.spanWidth);
fireWatchCollection("ngModel", mockScope.ngModel);
});
it("enforces a minimum outer span", function () {
mockScope.ngModel.outer.end =
mockScope.ngModel.outer.start - DAY * 100;
fireWatch(
"ngModel.outer.end",
mockScope.ngModel.outer.end
);
expect(mockScope.ngModel.outer.end)
.toBeGreaterThan(mockScope.ngModel.outer.start);
mockScope.ngModel.outer.start =
mockScope.ngModel.outer.end + DAY * 100;
fireWatch(
"ngModel.outer.start",
mockScope.ngModel.outer.start
);
expect(mockScope.ngModel.outer.end)
.toBeGreaterThan(mockScope.ngModel.outer.start);
});
it("enforces a minimum inner span when outer span changes", function () {
mockScope.ngModel.outer.end =
mockScope.ngModel.outer.start - DAY * 100;
fireWatch(
"ngModel.outer.end",
mockScope.ngModel.outer.end
);
expect(mockScope.ngModel.inner.end)
.toBeGreaterThan(mockScope.ngModel.inner.start);
});
describe("by typing", function () {
it("updates models", function () {
var newStart = "1977-05-25 17:30:00",
newEnd = "2015-12-18 03:30:00";
mockScope.boundsModel.start = newStart;
fireWatch("boundsModel.start", newStart);
expect(mockScope.ngModel.outer.start)
.toEqual(moment.utc(newStart).valueOf());
expect(mockScope.boundsModel.startValid)
.toBeTruthy();
mockScope.boundsModel.end = newEnd;
fireWatch("boundsModel.end", newEnd);
expect(mockScope.ngModel.outer.end)
.toEqual(moment.utc(newEnd).valueOf());
expect(mockScope.boundsModel.endValid)
.toBeTruthy();
});
it("displays error state", function () {
var newStart = "Not a date",
newEnd = "Definitely not a date",
oldStart = mockScope.ngModel.outer.start,
oldEnd = mockScope.ngModel.outer.end;
mockScope.boundsModel.start = newStart;
fireWatch("boundsModel.start", newStart);
expect(mockScope.ngModel.outer.start)
.toEqual(oldStart);
expect(mockScope.boundsModel.startValid)
.toBeFalsy();
mockScope.boundsModel.end = newEnd;
fireWatch("boundsModel.end", newEnd);
expect(mockScope.ngModel.outer.end)
.toEqual(oldEnd);
expect(mockScope.boundsModel.endValid)
.toBeFalsy();
});
it("does not modify user input", function () {
// Don't want the controller "fixing" bad or
// irregularly-formatted input out from under
// the user's fingertips.
var newStart = "Not a date",
newEnd = "2015-3-3 01:02:04",
oldStart = mockScope.ngModel.outer.start,
oldEnd = mockScope.ngModel.outer.end;
mockScope.boundsModel.start = newStart;
fireWatch("boundsModel.start", newStart);
expect(mockScope.boundsModel.start)
.toEqual(newStart);
mockScope.boundsModel.end = newEnd;
fireWatch("boundsModel.end", newEnd);
expect(mockScope.boundsModel.end)
.toEqual(newEnd);
});
});
});
});
}
);

View File

@ -0,0 +1,84 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,jasmine*/
define(
["../../src/directives/MCTClickElsewhere"],
function (MCTClickElsewhere) {
"use strict";
var JQLITE_METHODS = [ "on", "off", "find", "parent" ];
describe("The mct-click-elsewhere directive", function () {
var mockDocument,
mockScope,
mockElement,
testAttrs,
mockBody,
mockParentEl,
testRect,
mctClickElsewhere;
function testEvent(x, y) {
return {
pageX: x,
pageY: y,
preventDefault: jasmine.createSpy("preventDefault")
};
}
beforeEach(function () {
mockDocument =
jasmine.createSpyObj("$document", JQLITE_METHODS);
mockScope =
jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]);
mockElement =
jasmine.createSpyObj("element", JQLITE_METHODS);
mockBody =
jasmine.createSpyObj("body", JQLITE_METHODS);
mockParentEl =
jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
testAttrs = {
mctClickElsewhere: "some Angular expression"
};
testRect = {
left: 20,
top: 42,
width: 60,
height: 75
};
mockDocument.find.andReturn(mockBody);
mctClickElsewhere = new MCTClickElsewhere(mockDocument);
mctClickElsewhere.link(mockScope, mockElement, testAttrs);
});
it("is valid as an attribute", function () {
expect(mctClickElsewhere.restrict).toEqual("A");
});
});
}
);

View File

@ -0,0 +1,136 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,jasmine*/
define(
["../../src/directives/MCTPopup"],
function (MCTPopup) {
"use strict";
var JQLITE_METHODS = [ "on", "off", "find", "parent", "css", "append" ];
describe("The mct-popup directive", function () {
var mockCompile,
mockPopupService,
mockPopup,
mockScope,
mockElement,
testAttrs,
mockBody,
mockTransclude,
mockParentEl,
mockNewElement,
testRect,
mctPopup;
function testEvent(x, y) {
return {
pageX: x,
pageY: y,
preventDefault: jasmine.createSpy("preventDefault")
};
}
beforeEach(function () {
mockCompile =
jasmine.createSpy("$compile");
mockPopupService =
jasmine.createSpyObj("popupService", ["display"]);
mockPopup =
jasmine.createSpyObj("popup", ["dismiss"]);
mockScope =
jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]);
mockElement =
jasmine.createSpyObj("element", JQLITE_METHODS);
mockBody =
jasmine.createSpyObj("body", JQLITE_METHODS);
mockTransclude =
jasmine.createSpy("transclude");
mockParentEl =
jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
mockNewElement =
jasmine.createSpyObj("newElement", JQLITE_METHODS);
testAttrs = {
mctClickElsewhere: "some Angular expression"
};
testRect = {
left: 20,
top: 42,
width: 60,
height: 75
};
mockCompile.andCallFake(function () {
var mockFn = jasmine.createSpy();
mockFn.andReturn(mockNewElement);
return mockFn;
});
mockElement.parent.andReturn([mockParentEl]);
mockParentEl.getBoundingClientRect.andReturn(testRect);
mockPopupService.display.andReturn(mockPopup);
mctPopup = new MCTPopup(mockCompile, mockPopupService);
mctPopup.link(
mockScope,
mockElement,
testAttrs,
null,
mockTransclude
);
});
it("is valid as an element", function () {
expect(mctPopup.restrict).toEqual("E");
});
describe("creates an element which", function () {
it("displays as a popup", function () {
expect(mockPopupService.display).toHaveBeenCalledWith(
mockNewElement,
[ testRect.left, testRect.top ]
);
});
it("displays transcluded content", function () {
var mockClone =
jasmine.createSpyObj('clone', JQLITE_METHODS);
mockTransclude.mostRecentCall.args[0](mockClone);
expect(mockNewElement.append)
.toHaveBeenCalledWith(mockClone);
});
it("is removed when its containing scope is destroyed", function () {
expect(mockPopup.dismiss).not.toHaveBeenCalled();
mockScope.$on.calls.forEach(function (call) {
if (call.args[0] === '$destroy') {
call.args[1]();
}
});
expect(mockPopup.dismiss).toHaveBeenCalled();
});
});
});
}
);

View File

@ -0,0 +1,98 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../../src/services/PopupService"],
function (PopupService) {
'use strict';
describe("PopupService", function () {
var mockDocument,
testWindow,
mockBody,
mockElement,
popupService;
beforeEach(function () {
mockDocument = jasmine.createSpyObj('$document', [ 'find' ]);
testWindow = { innerWidth: 1000, innerHeight: 800 };
mockBody = jasmine.createSpyObj('body', [ 'append' ]);
mockElement = jasmine.createSpyObj('element', [
'css',
'remove'
]);
mockDocument.find.andCallFake(function (query) {
return query === 'body' && mockBody;
});
popupService = new PopupService(mockDocument, testWindow);
});
it("adds elements to the body of the document", function () {
popupService.display(mockElement, [ 0, 0 ]);
expect(mockBody.append).toHaveBeenCalledWith(mockElement);
});
describe("when positioned in appropriate quadrants", function () {
it("orients elements relative to the top-left", function () {
popupService.display(mockElement, [ 25, 50 ]);
expect(mockElement.css).toHaveBeenCalledWith({
position: 'absolute',
left: '25px',
top: '50px'
});
});
it("orients elements relative to the top-right", function () {
popupService.display(mockElement, [ 800, 50 ]);
expect(mockElement.css).toHaveBeenCalledWith({
position: 'absolute',
right: '200px',
top: '50px'
});
});
it("orients elements relative to the bottom-right", function () {
popupService.display(mockElement, [ 800, 650 ]);
expect(mockElement.css).toHaveBeenCalledWith({
position: 'absolute',
right: '200px',
bottom: '150px'
});
});
it("orients elements relative to the bottom-left", function () {
popupService.display(mockElement, [ 120, 650 ]);
expect(mockElement.css).toHaveBeenCalledWith({
position: 'absolute',
left: '120px',
bottom: '150px'
});
});
});
});
}
);

View File

@ -0,0 +1,74 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../../src/services/Popup"],
function (Popup) {
'use strict';
describe("Popup", function () {
var mockElement,
testStyles,
popup;
beforeEach(function () {
mockElement =
jasmine.createSpyObj('element', [ 'css', 'remove' ]);
testStyles = { left: '12px', top: '14px' };
popup = new Popup(mockElement, testStyles);
});
it("applies CSS styles when instantiated", function () {
expect(mockElement.css)
.toHaveBeenCalledWith(testStyles);
});
it("reports the orientation of the popup", function () {
var otherStyles = {
right: '12px',
bottom: '14px'
},
otherPopup = new Popup(mockElement, otherStyles);
expect(popup.goesLeft()).toBeFalsy();
expect(popup.goesRight()).toBeTruthy();
expect(popup.goesUp()).toBeFalsy();
expect(popup.goesDown()).toBeTruthy();
expect(otherPopup.goesLeft()).toBeTruthy();
expect(otherPopup.goesRight()).toBeFalsy();
expect(otherPopup.goesUp()).toBeTruthy();
expect(otherPopup.goesDown()).toBeFalsy();
});
it("removes elements when dismissed", function () {
expect(mockElement.remove).not.toHaveBeenCalled();
popup.dismiss();
expect(mockElement.remove).toHaveBeenCalled();
});
});
}
);

View File

@ -3,16 +3,22 @@
"controllers/BottomBarController",
"controllers/ClickAwayController",
"controllers/ContextMenuController",
"controllers/DateTimePickerController",
"controllers/GetterSetterController",
"controllers/SelectorController",
"controllers/SplitPaneController",
"controllers/TimeRangeController",
"controllers/ToggleController",
"controllers/TreeNodeController",
"controllers/ViewSwitcherController",
"directives/MCTClickElsewhere",
"directives/MCTContainer",
"directives/MCTDrag",
"directives/MCTPopup",
"directives/MCTResize",
"directives/MCTScroll",
"services/Popup",
"services/PopupService",
"services/UrlService",
"StyleSheetLoader"
]

View File

@ -45,13 +45,12 @@
"implementation": "services/InfoService.js",
"depends": [
"$compile",
"$document",
"$window",
"$rootScope",
"popupService",
"agentService"
]
}
],
],
"constants": [
{
"key": "INFO_HOVER_DELAY",
@ -66,4 +65,4 @@
}
]
}
}
}

View File

@ -31,13 +31,19 @@ define({
BUBBLE_TEMPLATE: "<mct-container key=\"bubble\" " +
"bubble-title=\"{{bubbleTitle}}\" " +
"bubble-layout=\"{{bubbleLayout}}\" " +
"class=\"bubble-container\">" +
"<mct-include key=\"bubbleTemplate\" ng-model=\"bubbleModel\">" +
"class=\"bubble-container\">" +
"<mct-include key=\"bubbleTemplate\" " +
"ng-model=\"bubbleModel\">" +
"</mct-include>" +
"</mct-container>",
// Pixel offset for bubble, to align arrow position
BUBBLE_OFFSET: [ 0, -26 ],
// Max width and margins allowed for bubbles; defined in /platform/commonUI/general/res/sass/_constants.scss
BUBBLE_MARGIN_LR: 10,
BUBBLE_MAX_WIDTH: 300
// Options and classes for bubble
BUBBLE_OPTIONS: {
offsetX: 0,
offsetY: -26
},
BUBBLE_MOBILE_POSITION: [ 0, -25 ],
// Max width and margins allowed for bubbles;
// defined in /platform/commonUI/general/res/sass/_constants.scss
BUBBLE_MARGIN_LR: 10,
BUBBLE_MAX_WIDTH: 300
});

View File

@ -27,18 +27,18 @@ define(
"use strict";
var BUBBLE_TEMPLATE = InfoConstants.BUBBLE_TEMPLATE,
OFFSET = InfoConstants.BUBBLE_OFFSET;
MOBILE_POSITION = InfoConstants.BUBBLE_MOBILE_POSITION,
OPTIONS = InfoConstants.BUBBLE_OPTIONS;
/**
* Displays informative content ("info bubbles") for the user.
* @memberof platform/commonUI/inspect
* @constructor
*/
function InfoService($compile, $document, $window, $rootScope, agentService) {
function InfoService($compile, $rootScope, popupService, agentService) {
this.$compile = $compile;
this.$document = $document;
this.$window = $window;
this.$rootScope = $rootScope;
this.popupService = popupService;
this.agentService = agentService;
}
@ -55,53 +55,47 @@ define(
*/
InfoService.prototype.display = function (templateKey, title, content, position) {
var $compile = this.$compile,
$document = this.$document,
$window = this.$window,
$rootScope = this.$rootScope,
body = $document.find('body'),
scope = $rootScope.$new(),
winDim = [$window.innerWidth, $window.innerHeight],
bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR + InfoConstants.BUBBLE_MAX_WIDTH,
goLeft = position[0] > (winDim[0] - bubbleSpaceLR),
goUp = position[1] > (winDim[1] / 2),
span = $compile('<span></span>')(scope),
bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR +
InfoConstants.BUBBLE_MAX_WIDTH,
options,
popup,
bubble;
options = Object.create(OPTIONS);
options.marginX = -bubbleSpaceLR;
// On a phone, bubble takes up more screen real estate,
// so position it differently (toward the bottom)
if (this.agentService.isPhone(navigator.userAgent)) {
position = MOBILE_POSITION;
options = {};
}
popup = this.popupService.display(span, position, options);
// Pass model & container parameters into the scope
scope.bubbleModel = content;
scope.bubbleTemplate = templateKey;
scope.bubbleLayout = (goUp ? 'arw-btm' : 'arw-top') + ' ' +
(goLeft ? 'arw-right' : 'arw-left');
scope.bubbleTitle = title;
// Style the bubble according to how it was positioned
scope.bubbleLayout = [
popup.goesUp() ? 'arw-btm' : 'arw-top',
popup.goesLeft() ? 'arw-right' : 'arw-left'
].join(' ');
scope.bubbleLayout = 'arw-top arw-left';
// Create the context menu
// Create the info bubble, now that we know how to
// point the arrow...
bubble = $compile(BUBBLE_TEMPLATE)(scope);
span.append(bubble);
// Position the bubble
bubble.css('position', 'absolute');
if (this.agentService.isPhone(navigator.userAgent)) {
bubble.css('right', '0px');
bubble.css('left', '0px');
bubble.css('top', 'auto');
bubble.css('bottom', '25px');
} else {
if (goLeft) {
bubble.css('right', (winDim[0] - position[0] + OFFSET[0]) + 'px');
} else {
bubble.css('left', position[0] + OFFSET[0] + 'px');
}
if (goUp) {
bubble.css('bottom', (winDim[1] - position[1] + OFFSET[1]) + 'px');
} else {
bubble.css('top', position[1] + OFFSET[1] + 'px');
}
}
// Add the menu to the body
body.append(bubble);
// Return a function to dismiss the bubble
return function () {
bubble.remove();
// Return a function to dismiss the info bubble
return function dismiss() {
popup.dismiss();
scope.$destroy();
};
};

View File

@ -28,117 +28,85 @@ define(
describe("The info service", function () {
var mockCompile,
mockDocument,
testWindow,
mockRootScope,
mockPopupService,
mockAgentService,
mockCompiledTemplate,
testScope,
mockBody,
mockElement,
mockScope,
mockElements,
mockPopup,
service;
beforeEach(function () {
mockCompile = jasmine.createSpy('$compile');
mockDocument = jasmine.createSpyObj('$document', ['find']);
testWindow = { innerWidth: 1000, innerHeight: 100 };
mockRootScope = jasmine.createSpyObj('$rootScope', ['$new']);
mockAgentService = jasmine.createSpyObj('agentService', ['isMobile', 'isPhone']);
mockCompiledTemplate = jasmine.createSpy('template');
testScope = {};
mockBody = jasmine.createSpyObj('body', ['append']);
mockElement = jasmine.createSpyObj('element', ['css', 'remove']);
mockPopupService = jasmine.createSpyObj(
'popupService',
['display']
);
mockPopup = jasmine.createSpyObj('popup', [
'dismiss',
'goesLeft',
'goesRight',
'goesUp',
'goesDown'
]);
mockDocument.find.andCallFake(function (tag) {
return tag === 'body' ? mockBody : undefined;
mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
mockElements = [];
mockPopupService.display.andReturn(mockPopup);
mockCompile.andCallFake(function () {
var mockCompiledTemplate = jasmine.createSpy('template'),
mockElement = jasmine.createSpyObj('element', [
'css',
'remove',
'append'
]);
mockCompiledTemplate.andReturn(mockElement);
mockElements.push(mockElement);
return mockCompiledTemplate;
});
mockCompile.andReturn(mockCompiledTemplate);
mockCompiledTemplate.andReturn(mockElement);
mockRootScope.$new.andReturn(testScope);
mockRootScope.$new.andReturn(mockScope);
service = new InfoService(
mockCompile,
mockDocument,
testWindow,
mockRootScope,
mockPopupService,
mockAgentService
);
});
it("creates elements and appends them to the body to display", function () {
service.display('', '', {}, [0, 0]);
expect(mockBody.append).toHaveBeenCalledWith(mockElement);
it("creates elements and displays them as popups", function () {
service.display('', '', {}, [123, 456]);
expect(mockPopupService.display).toHaveBeenCalledWith(
mockElements[0],
[ 123, 456 ],
jasmine.any(Object)
);
});
it("provides a function to remove displayed info bubbles", function () {
var fn = service.display('', '', {}, [0, 0]);
expect(mockElement.remove).not.toHaveBeenCalled();
expect(mockPopup.dismiss).not.toHaveBeenCalled();
fn();
expect(mockElement.remove).toHaveBeenCalled();
expect(mockPopup.dismiss).toHaveBeenCalled();
});
describe("depending on mouse position", function () {
// Positioning should vary based on quadrant in window,
// which is 1000 x 100 in this test case.
it("displays from the top-left in the top-left quadrant", function () {
service.display('', '', {}, [250, 25]);
expect(mockElement.css).toHaveBeenCalledWith(
'left',
(250 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
);
expect(mockElement.css).toHaveBeenCalledWith(
'top',
(25 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
);
});
it("displays from the top-right in the top-right quadrant", function () {
service.display('', '', {}, [700, 25]);
expect(mockElement.css).toHaveBeenCalledWith(
'right',
(300 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
);
expect(mockElement.css).toHaveBeenCalledWith(
'top',
(25 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
);
});
it("displays from the bottom-left in the bottom-left quadrant", function () {
service.display('', '', {}, [250, 70]);
expect(mockElement.css).toHaveBeenCalledWith(
'left',
(250 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
);
expect(mockElement.css).toHaveBeenCalledWith(
'bottom',
(30 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
);
});
it("displays from the bottom-right in the bottom-right quadrant", function () {
service.display('', '', {}, [800, 60]);
expect(mockElement.css).toHaveBeenCalledWith(
'right',
(200 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
);
expect(mockElement.css).toHaveBeenCalledWith(
'bottom',
(40 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
);
});
it("when on phone device, positioning is always on bottom", function () {
mockAgentService.isPhone.andReturn(true);
service = new InfoService(
mockCompile,
mockDocument,
testWindow,
mockRootScope,
mockAgentService
);
service.display('', '', {}, [0, 0]);
});
it("when on phone device, positions at bottom", function () {
mockAgentService.isPhone.andReturn(true);
service = new InfoService(
mockCompile,
mockRootScope,
mockPopupService,
mockAgentService
);
service.display('', '', {}, [123, 456]);
expect(mockPopupService.display).toHaveBeenCalledWith(
mockElements[0],
[ 0, -25 ],
jasmine.any(Object)
);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -7,12 +7,14 @@ $colorKey: #0099cc;
$colorKeySelectedBg: #005177;
$colorKeyFg: #fff;
$colorInteriorBorder: rgba($colorBodyFg, 0.1);
$colorA: #ccc;
$colorAHov: #fff;
$contrastRatioPercent: 7%;
$basicCr: 3px;
$controlCr: 3px;
$smallCr: 2px;
// Buttons
// Buttons and Controls
$colorBtnBg: pullForward($colorBodyBg, $contrastRatioPercent); //
$colorBtnFg: $colorBodyFg;
$colorBtnMajorBg: $colorKey;
@ -20,6 +22,18 @@ $colorBtnMajorFg: $colorKeyFg;
$colorBtnIcon: $colorKey;
$colorInvokeMenu: #fff;
$contrastInvokeMenuPercent: 20%;
$shdwBtns: rgba(black, 0.2) 0 1px 2px;
$sliderColorBase: $colorKey;
$sliderColorRangeHolder: rgba(black, 0.1);
$sliderColorRange: rgba($sliderColorBase, 0.3);
$sliderColorRangeHov: rgba($sliderColorBase, 0.5);
$sliderColorKnob: rgba($sliderColorBase, 0.6);
$sliderColorKnobHov: $sliderColorBase;
$sliderColorRangeValHovBg: rgba($sliderColorBase, 0.1);
$sliderColorRangeValHovFg: $colorKeyFg;
$sliderKnobW: nth($ueTimeControlH,2)/2;
$timeControllerToiLineColor: #00c2ff;
$timeControllerToiLineColorHov: #fff;
// General Colors
$colorAlt1: #ffc700;
@ -32,6 +46,7 @@ $colorGridLines: rgba(#fff, 0.05);
$colorInvokeMenu: #fff;
$colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: pullForward($colorObjHdrTxt, 20%);
$colorTick: rgba(white, 0.2);
// Menu colors
$colorMenuBg: pullForward($colorBodyBg, 23%);
@ -111,16 +126,17 @@ $colorItemBgSelected: $colorKey;
$colorTabBorder: pullForward($colorBodyBg, 10%);
$colorTabBodyBg: darken($colorBodyBg, 10%);
$colorTabBodyFg: lighten($colorTabBodyBg, 40%);
$colorTabHeaderBg: lighten($colorBodyBg, 10%);
$colorTabHeaderFg: lighten($colorTabHeaderBg, 40%);
$colorTabHeaderBg: rgba(white, 0.1); // lighten($colorBodyBg, 10%);
$colorTabHeaderFg: $colorBodyFg; //lighten($colorTabHeaderBg, 40%);
$colorTabHeaderBorder: $colorBodyBg;
// Plot
$colorPlotBg: rgba(black, 0.1);
$colorPlotFg: $colorBodyFg;
$colorPlotHash: rgba(white, 0.2);
$colorPlotHash: $colorTick;
$stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
// Tree
$colorItemTreeIcon: $colorKey;
@ -151,5 +167,16 @@ $colorGrippyInteriorHover: $colorKey;
// Mobile
$colorMobilePaneLeft: darken($colorBodyBg, 5%);
// Datetime Picker
$colorCalCellHovBg: $colorKey;
$colorCalCellHovFg: $colorKeyFg;
$colorCalCellSelectedBg: $colorItemTreeSelectedBg;
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
$colorCalCellInMonthBg: pushBack($colorMenuBg, 5%);
// About Screen
$colorAboutLink: #84b3ff;
$colorAboutLink: #84b3ff;
// Loading
$colorLoadingBg: rgba($colorBodyFg, 0.2);
$colorLoadingFg: $colorAlt1;

View File

@ -1,13 +1,13 @@
@mixin containerSubtle($bg: $colorBodyBg, $fg: $colorBodyFg, $hover: false) {
@include containerBase($bg, $fg);
@include background-image(linear-gradient(lighten($bg, 5%), $bg));
@include boxShdwSubtle();
@include boxShdw($shdwBtns);
}
@mixin btnSubtle($bg: $colorBodyBg, $bgHov: none, $fg: $colorBodyFg, $ic: $colorBtnIcon) {
@mixin btnSubtle($bg: $colorBodyBg, $bgHov: none, $fg: $colorBodyFg, $ic: $colorBtnIcon) {
@include containerSubtle($bg, $fg);
@include btnBase($bg, linear-gradient(lighten($bg, 15%), lighten($bg, 10%)), $fg, $ic);
@include text-shadow(rgba(black, 0.3) 0 1px 1px);
@include text-shadow($shdwItemText);
}
@function pullForward($c: $colorBodyBg, $p: 20%) {

File diff suppressed because it is too large Load Diff

View File

@ -7,12 +7,14 @@ $colorKey: #0099cc;
$colorKeySelectedBg: $colorKey;
$colorKeyFg: #fff;
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #999;
$colorAHov: $colorKey;
$contrastRatioPercent: 40%;
$basicCr: 4px;
$controlCr: $basicCr;
$smallCr: 3px;
// Buttons
// Buttons and Controls
$colorBtnBg: pullForward($colorBodyBg, $contrastRatioPercent);
$colorBtnFg: #fff;
$colorBtnMajorBg: $colorKey;
@ -20,10 +22,22 @@ $colorBtnMajorFg: $colorKeyFg;
$colorBtnIcon: #eee;
$colorInvokeMenu: #000;
$contrastInvokeMenuPercent: 40%;
$shdwBtns: none;
$sliderColorBase: $colorKey;
$sliderColorRangeHolder: rgba(black, 0.07);
$sliderColorRange: rgba($sliderColorBase, 0.2);
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
$sliderColorKnob: rgba($sliderColorBase, 0.5);
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
$sliderColorRangeValHovBg: $sliderColorRange; //rgba($sliderColorBase, 0.1);
$sliderColorRangeValHovFg: $colorBodyFg;
$sliderKnobW: nth($ueTimeControlH,2)/2;
$timeControllerToiLineColor: $colorBodyFg;
$timeControllerToiLineColorHov: #0052b5;
// General Colors
$colorAlt1: #ff6600;
$colorAlert: #ff533a;
$colorAlt1: #776ba2;
$colorAlert: #ff3c00;
$colorIconLink: #49dedb;
$colorPausedBg: #ff9900;
$colorPausedFg: #fff;
@ -32,6 +46,7 @@ $colorGridLines: rgba(#000, 0.05);
$colorInvokeMenu: #fff;
$colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: pushBack($colorObjHdrTxt, 30%);
$colorTick: rgba(black, 0.2);
// Menu colors
$colorMenuBg: pushBack($colorBodyBg, 10%);
@ -57,20 +72,6 @@ $colorInputBg: $colorGenBg;
$colorInputFg: $colorBodyFg;
$colorFormText: pushBack($colorBodyFg, 10%);
$colorInputIcon: pushBack($colorBodyFg, 25%);
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #fff;
$colorStatusDefault: #ccc;
$colorStatusInfo: #60ba7b;
$colorStatusAlert: #ffb66c;
$colorStatusError: #c96b68;
$colorProgressBarOuter: rgba(#000, 0.1);
$colorProgressBarAmt: #0a0;
$progressBarHOverlay: 15px;
$progressBarStripeW: 20px;
$shdwStatusIc: rgba(white, 0.8) 0 0px 5px;
// Selects
$colorSelectBg: #ddd;
$colorSelectFg: $colorBodyFg;
@ -118,9 +119,10 @@ $colorTabHeaderBorder: $colorBodyBg;
// Plot
$colorPlotBg: rgba(black, 0.05);
$colorPlotFg: $colorBodyFg;
$colorPlotHash: rgba(black, 0.2);
$colorPlotHash: $colorTick;
$stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
// Tree
$colorItemTreeIcon: $colorKey;
@ -151,5 +153,16 @@ $colorGrippyInteriorHover: $colorBodyBg;
// Mobile
$colorMobilePaneLeft: darken($colorBodyBg, 2%);
// Datetime Picker, Calendar
$colorCalCellHovBg: $colorKey;
$colorCalCellHovFg: $colorKeyFg;
$colorCalCellSelectedBg: $colorItemTreeSelectedBg;
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
// About Screen
$colorAboutLink: #84b3ff;
$colorAboutLink: #84b3ff;
// Loading
$colorLoadingFg: $colorAlt1;
$colorLoadingBg: rgba($colorLoadingFg, 0.1);

View File

@ -1,5 +1,6 @@
@mixin containerSubtle($bg: $colorBodyBg, $fg: $colorBodyFg) {
@include containerBase($bg, $fg);
@include boxShdw($shdwBtns);
}
@mixin btnSubtle($bg: $colorBtnBg, $bgHov: none, $fg: $colorBtnFg, $ic: $colorBtnIcon) {

View File

@ -66,6 +66,7 @@
"depends": [
"persistenceService",
"$q",
"now",
"PERSISTENCE_SPACE",
"ADDITIONAL_PERSISTENCE_SPACES"
]

View File

@ -29,7 +29,8 @@ define(
function () {
"use strict";
var TOPIC_PREFIX = "mutation:";
var GENERAL_TOPIC = "mutation",
TOPIC_PREFIX = "mutation:";
// Utility function to overwrite a destination object
// with the contents of a source object.
@ -78,7 +79,11 @@ define(
* @implements {Capability}
*/
function MutationCapability(topic, now, domainObject) {
this.mutationTopic = topic(TOPIC_PREFIX + domainObject.getId());
this.generalMutationTopic =
topic(GENERAL_TOPIC);
this.specificMutationTopic =
topic(TOPIC_PREFIX + domainObject.getId());
this.now = now;
this.domainObject = domainObject;
}
@ -115,11 +120,17 @@ define(
// mutator function has a temporary copy to work with.
var domainObject = this.domainObject,
now = this.now,
t = this.mutationTopic,
generalTopic = this.generalMutationTopic,
specificTopic = this.specificMutationTopic,
model = domainObject.getModel(),
clone = JSON.parse(JSON.stringify(model)),
useTimestamp = arguments.length > 1;
function notifyListeners(model) {
generalTopic.notify(domainObject);
specificTopic.notify(model);
}
// Function to handle copying values to the actual
function handleMutation(mutationResult) {
// If mutation result was undefined, just use
@ -136,7 +147,7 @@ define(
copyValues(model, result);
}
model.modified = useTimestamp ? timestamp : now();
t.notify(model);
notifyListeners(model);
}
// Report the result of the mutation
@ -158,7 +169,7 @@ define(
* @memberof platform/core.MutationCapability#
*/
MutationCapability.prototype.listen = function (listener) {
return this.mutationTopic.listen(listener);
return this.specificMutationTopic.listen(listener);
};
/**

View File

@ -39,14 +39,16 @@ define(
* @param {PersistenceService} persistenceService the service in which
* domain object models are persisted.
* @param $q Angular's $q service, for working with promises
* @param {function} now a function which provides the current time
* @param {string} space the name of the persistence space(s)
* from which models should be retrieved.
* @param {string} spaces additional persistence spaces to use
*/
function PersistedModelProvider(persistenceService, $q, space, spaces) {
function PersistedModelProvider(persistenceService, $q, now, space, spaces) {
this.persistenceService = persistenceService;
this.$q = $q;
this.spaces = [space].concat(spaces || []);
this.now = now;
}
// Take the most recently modified model, for cases where
@ -61,7 +63,9 @@ define(
PersistedModelProvider.prototype.getModels = function (ids) {
var persistenceService = this.persistenceService,
$q = this.$q,
spaces = this.spaces;
spaces = this.spaces,
space = this.space,
now = this.now;
// Load a single object model from any persistence spaces
function loadModel(id) {
@ -72,11 +76,24 @@ define(
});
}
// Ensure that models read from persistence have some
// sensible timestamp indicating they've been persisted.
function addPersistedTimestamp(model) {
if (model && (model.persisted === undefined)) {
model.persisted = model.modified !== undefined ?
model.modified : now();
}
return model;
}
// Package the result as id->model
function packageResult(models) {
var result = {};
ids.forEach(function (id, index) {
result[id] = models[index];
if (models[index]) {
result[id] = addPersistedTimestamp(models[index]);
}
});
return result;
}

View File

@ -36,11 +36,16 @@ define(
*
* Returns a function that, when invoked, will invoke `fn` after
* `delay` milliseconds, only if no other invocations are pending.
* The optional argument `apply` determines whether.
* The optional argument `apply` determines whether or not a
* digest cycle should be triggered.
*
* The returned function will itself return a `Promise` which will
* resolve to the returned value of `fn` whenever that is invoked.
*
* In cases where arguments are provided, only the most recent
* set of arguments will be passed on to the throttled function
* at the time it is executed.
*
* @returns {Function}
* @memberof platform/core
*/
@ -56,12 +61,14 @@ define(
* @memberof platform/core.Throttle#
*/
return function (fn, delay, apply) {
var activeTimeout;
var promise,
args = [];
// Clear active timeout, so that next invocation starts
// a new one.
function clearActiveTimeout() {
activeTimeout = undefined;
function invoke() {
// Clear the active timeout so a new one starts next time.
promise = undefined;
// Invoke the function with the latest supplied arguments.
return fn.apply(null, args);
}
// Defaults
@ -69,14 +76,13 @@ define(
apply = apply || false;
return function () {
// Store arguments from this invocation
args = Array.prototype.slice.apply(arguments, [0]);
// Start a timeout if needed
if (!activeTimeout) {
activeTimeout = $timeout(fn, delay, apply);
activeTimeout.then(clearActiveTimeout);
}
promise = promise || $timeout(invoke, delay, apply);
// Return whichever timeout is active (to get
// a promise for the results of fn)
return activeTimeout;
return promise;
};
};
}

View File

@ -35,6 +35,7 @@ define(
SPACE = "space0",
spaces = [ "space1" ],
modTimes,
mockNow,
provider;
function mockPromise(value) {
@ -55,19 +56,33 @@ define(
beforeEach(function () {
modTimes = {};
mockQ = { when: mockPromise, all: mockAll };
mockPersistenceService = {
readObject: function (space, id) {
mockPersistenceService = jasmine.createSpyObj(
'persistenceService',
[
'createObject',
'readObject',
'updateObject',
'deleteObject',
'listSpaces',
'listObjects'
]
);
mockNow = jasmine.createSpy("now");
mockPersistenceService.readObject
.andCallFake(function (space, id) {
return mockPromise({
space: space,
id: id,
modified: (modTimes[space] || {})[id]
modified: (modTimes[space] || {})[id],
persisted: 0
});
}
};
});
provider = new PersistedModelProvider(
mockPersistenceService,
mockQ,
mockNow,
SPACE,
spaces
);
@ -81,12 +96,13 @@ define(
});
expect(models).toEqual({
a: { space: SPACE, id: "a" },
x: { space: SPACE, id: "x" },
zz: { space: SPACE, id: "zz" }
a: { space: SPACE, id: "a", persisted: 0 },
x: { space: SPACE, id: "x", persisted: 0 },
zz: { space: SPACE, id: "zz", persisted: 0 }
});
});
it("reads object models from multiple spaces", function () {
var models;
@ -99,9 +115,36 @@ define(
});
expect(models).toEqual({
a: { space: SPACE, id: "a" },
x: { space: 'space1', id: "x", modified: 12321 },
zz: { space: SPACE, id: "zz" }
a: { space: SPACE, id: "a", persisted: 0 },
x: { space: 'space1', id: "x", modified: 12321, persisted: 0 },
zz: { space: SPACE, id: "zz", persisted: 0 }
});
});
it("ensures that persisted timestamps are present", function () {
var mockCallback = jasmine.createSpy("callback"),
testModels = {
a: { modified: 123, persisted: 1984, name: "A" },
b: { persisted: 1977, name: "B" },
c: { modified: 42, name: "C" },
d: { name: "D" }
};
mockPersistenceService.readObject.andCallFake(
function (space, id) {
return mockPromise(testModels[id]);
}
);
mockNow.andReturn(12321);
provider.getModels(Object.keys(testModels)).then(mockCallback);
expect(mockCallback).toHaveBeenCalledWith({
a: { modified: 123, persisted: 1984, name: "A" },
b: { persisted: 1977, name: "B" },
c: { modified: 42, persisted: 42, name: "C" },
d: { persisted: 12321, name: "D" }
});
});

View File

@ -45,7 +45,9 @@ define(
// Verify precondition: Not called at throttle-time
expect(mockTimeout).not.toHaveBeenCalled();
expect(throttled()).toEqual(mockPromise);
expect(mockTimeout).toHaveBeenCalledWith(mockFn, 0, false);
expect(mockFn).not.toHaveBeenCalled();
expect(mockTimeout)
.toHaveBeenCalledWith(jasmine.any(Function), 0, false);
});
it("schedules only one timeout at a time", function () {
@ -59,10 +61,11 @@ define(
it("schedules additional invocations after resolution", function () {
var throttled = throttle(mockFn);
throttled();
mockPromise.then.mostRecentCall.args[0](); // Resolve timeout
mockTimeout.mostRecentCall.args[0](); // Resolve timeout
throttled();
mockPromise.then.mostRecentCall.args[0]();
mockTimeout.mostRecentCall.args[0]();
throttled();
mockTimeout.mostRecentCall.args[0]();
expect(mockTimeout.calls.length).toEqual(3);
});
});

View File

@ -31,6 +31,14 @@
"category": "contextual",
"implementation": "actions/LinkAction.js",
"depends": ["locationService", "linkService"]
},
{
"key": "follow",
"name": "Go To Original",
"description": "Go to the original, un-linked instance of this object.",
"glyph": "\u00F4",
"category": "contextual",
"implementation": "actions/GoToOriginalAction.js"
}
],
"components": [
@ -53,7 +61,8 @@
"key": "location",
"name": "Location Capability",
"description": "Provides a capability for retrieving the location of an object based upon it's context.",
"implementation": "capabilities/LocationCapability"
"implementation": "capabilities/LocationCapability",
"depends": [ "$q", "$injector" ]
}
],
"services": [

View File

@ -0,0 +1,62 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define */
define(
function () {
"use strict";
/**
* Implements the "Go To Original" action, which follows a link back
* to an original instance of an object.
*
* @implements {Action}
* @constructor
* @private
* @memberof platform/entanglement
* @param {ActionContext} context the context in which the action
* will be performed
*/
function GoToOriginalAction(context) {
this.domainObject = context.domainObject;
}
GoToOriginalAction.prototype.perform = function () {
return this.domainObject.getCapability("location").getOriginal()
.then(function (originalObject) {
var actionCapability =
originalObject.getCapability("action");
return actionCapability &&
actionCapability.perform("navigate");
});
};
GoToOriginalAction.appliesTo = function (context) {
var domainObject = context.domainObject;
return domainObject && domainObject.hasCapability("location")
&& domainObject.getCapability("location").isLink();
};
return GoToOriginalAction;
}
);

View File

@ -1,3 +1,25 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define */
define(
@ -12,11 +34,41 @@ define(
*
* @constructor
*/
function LocationCapability(domainObject) {
function LocationCapability($q, $injector, domainObject) {
this.domainObject = domainObject;
this.$q = $q;
this.$injector = $injector;
return this;
}
/**
* Get an instance of this domain object in its original location.
*
* @returns {Promise.<DomainObject>} a promise for the original
* instance of this domain object
*/
LocationCapability.prototype.getOriginal = function () {
var id;
if (this.isOriginal()) {
return this.$q.when(this.domainObject);
}
id = this.domainObject.getId();
this.objectService =
this.objectService || this.$injector.get("objectService");
// Assume that an object will be correctly contextualized when
// loaded directly from the object service; this is true
// so long as LocatingObjectDecorator is present, and that
// decorator is also contained in this bundle.
return this.objectService.getObjects([id])
.then(function (objects) {
return objects[id];
});
};
/**
* Set the primary location (the parent id) of the current domain
* object.
@ -78,10 +130,6 @@ define(
return !this.isLink();
};
function createLocationCapability(domainObject) {
return new LocationCapability(domainObject);
}
return createLocationCapability;
return LocationCapability;
}
);

View File

@ -0,0 +1,95 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,beforeEach,it,jasmine,expect */
define(
[
'../../src/actions/GoToOriginalAction',
'../DomainObjectFactory',
'../ControlledPromise'
],
function (GoToOriginalAction, domainObjectFactory, ControlledPromise) {
'use strict';
describe("The 'go to original' action", function () {
var testContext,
originalDomainObject,
mockLocationCapability,
mockOriginalActionCapability,
originalPromise,
action;
beforeEach(function () {
mockLocationCapability = jasmine.createSpyObj(
'location',
[ 'isLink', 'isOriginal', 'getOriginal' ]
);
mockOriginalActionCapability = jasmine.createSpyObj(
'action',
[ 'perform', 'getActions' ]
);
originalPromise = new ControlledPromise();
mockLocationCapability.getOriginal.andReturn(originalPromise);
mockLocationCapability.isLink.andReturn(true);
mockLocationCapability.isOriginal.andCallFake(function () {
return !mockLocationCapability.isLink();
});
testContext = {
domainObject: domainObjectFactory({
capabilities: {
location: mockLocationCapability
}
})
};
originalDomainObject = domainObjectFactory({
capabilities: {
action: mockOriginalActionCapability
}
});
action = new GoToOriginalAction(testContext);
});
it("is applicable to links", function () {
expect(GoToOriginalAction.appliesTo(testContext))
.toBeTruthy();
});
it("is not applicable to originals", function () {
mockLocationCapability.isLink.andReturn(false);
expect(GoToOriginalAction.appliesTo(testContext))
.toBeFalsy();
});
it("navigates to original objects when performed", function () {
expect(mockOriginalActionCapability.perform)
.not.toHaveBeenCalled();
action.perform();
originalPromise.resolve(originalDomainObject);
expect(mockOriginalActionCapability.perform)
.toHaveBeenCalledWith('navigate');
});
});
}
);

View File

@ -1,3 +1,25 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,jasmine */
define(
@ -7,6 +29,7 @@ define(
'../ControlledPromise'
],
function (LocationCapability, domainObjectFactory, ControlledPromise) {
'use strict';
describe("LocationCapability", function () {
@ -14,13 +37,17 @@ define(
var locationCapability,
persistencePromise,
mutationPromise,
mockQ,
mockInjector,
mockObjectService,
domainObject;
beforeEach(function () {
domainObject = domainObjectFactory({
id: "testObject",
capabilities: {
context: {
getParent: function() {
getParent: function () {
return domainObjectFactory({id: 'root'});
}
},
@ -35,6 +62,11 @@ define(
}
});
mockQ = jasmine.createSpyObj("$q", ["when"]);
mockInjector = jasmine.createSpyObj("$injector", ["get"]);
mockObjectService =
jasmine.createSpyObj("objectService", ["getObjects"]);
persistencePromise = new ControlledPromise();
domainObject.capabilities.persistence.persist.andReturn(
persistencePromise
@ -49,7 +81,11 @@ define(
}
);
locationCapability = new LocationCapability(domainObject);
locationCapability = new LocationCapability(
mockQ,
mockInjector,
domainObject
);
});
it("returns contextual location", function () {
@ -88,6 +124,57 @@ define(
expect(whenComplete).toHaveBeenCalled();
});
describe("when used to load an original instance", function () {
var objectPromise,
qPromise,
originalObjects,
mockCallback;
function resolvePromises() {
if (mockQ.when.calls.length > 0) {
qPromise.resolve(mockQ.when.mostRecentCall.args[0]);
}
if (mockObjectService.getObjects.calls.length > 0) {
objectPromise.resolve(originalObjects);
}
}
beforeEach(function () {
objectPromise = new ControlledPromise();
qPromise = new ControlledPromise();
originalObjects = {
testObject: domainObjectFactory()
};
mockInjector.get.andCallFake(function (key) {
return key === 'objectService' && mockObjectService;
});
mockObjectService.getObjects.andReturn(objectPromise);
mockQ.when.andReturn(qPromise);
mockCallback = jasmine.createSpy('callback');
});
it("provides originals directly", function () {
domainObject.model.location = 'root';
locationCapability.getOriginal().then(mockCallback);
expect(mockCallback).not.toHaveBeenCalled();
resolvePromises();
expect(mockCallback)
.toHaveBeenCalledWith(domainObject);
});
it("loads from the object service for links", function () {
domainObject.model.location = 'some-other-root';
locationCapability.getOriginal().then(mockCallback);
expect(mockCallback).not.toHaveBeenCalled();
resolvePromises();
expect(mockCallback)
.toHaveBeenCalledWith(originalObjects.testObject);
});
});
});
});
}

View File

@ -1,6 +1,9 @@
[
"actions/AbstractComposeAction",
"actions/CopyAction",
"actions/GoToOriginalAction",
"actions/LinkAction",
"actions/MoveAction",
"services/CopyService",
"services/LinkService",
"services/MoveService",

View File

@ -0,0 +1,9 @@
Provides the time conductor, a control which appears at the
bottom of the screen allowing telemetry start and end times
to be modified.
Note that the term "time controller" is generally preferred
outside of the code base (e.g. in UI documents, issues, etc.);
the term "time conductor" is being used in code to avoid
confusion with "controllers" in the Model-View-Controller
sense.

View File

@ -0,0 +1,46 @@
{
"extensions": {
"representers": [
{
"implementation": "ConductorRepresenter.js",
"depends": [
"throttle",
"conductorService",
"$compile",
"views[]"
]
}
],
"components": [
{
"type": "decorator",
"provides": "telemetryService",
"implementation": "ConductorTelemetryDecorator.js",
"depends": [ "conductorService" ]
}
],
"services": [
{
"key": "conductorService",
"implementation": "ConductorService.js",
"depends": [ "now", "TIME_CONDUCTOR_DOMAINS" ]
}
],
"templates": [
{
"key": "time-conductor",
"templateUrl": "templates/time-conductor.html"
}
],
"constants": [
{
"key": "TIME_CONDUCTOR_DOMAINS",
"value": [
{ "key": "time", "name": "Time" },
{ "key": "yesterday", "name": "Yesterday" }
],
"comment": "Placeholder; to be replaced by inspection of available domains."
}
]
}
}

View File

@ -0,0 +1,10 @@
<mct-include key="'time-controller'"
ng-model='ngModel.conductor'>
</mct-include>
<mct-control key="'select'"
ng-model='ngModel'
field="'domain'"
options="ngModel.options"
style="position: absolute; right: 0px; bottom: 46px;"
>
</mct-control>

View File

@ -0,0 +1,201 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
var TEMPLATE = [
"<mct-include key=\"'time-conductor'\" ng-model='ngModel' class='l-time-controller'>",
"</mct-include>"
].join(''),
THROTTLE_MS = 200,
GLOBAL_SHOWING = false;
/**
* The ConductorRepresenter attaches the universal time conductor
* to views.
*
* @implements {Representer}
* @constructor
* @memberof platform/features/conductor
* @param {Function} throttle a function used to reduce the frequency
* of function invocations
* @param {platform/features/conductor.ConductorService} conductorService
* service which provides the active time conductor
* @param $compile Angular's $compile
* @param {ViewDefinition[]} views all defined views
* @param {Scope} the scope of the representation
* @param element the jqLite-wrapped representation element
*/
function ConductorRepresenter(
throttle,
conductorService,
$compile,
views,
scope,
element
) {
this.throttle = throttle;
this.scope = scope;
this.conductorService = conductorService;
this.element = element;
this.views = views;
this.$compile = $compile;
}
// Update the time conductor from the scope
ConductorRepresenter.prototype.wireScope = function () {
var conductor = this.conductorService.getConductor(),
conductorScope = this.conductorScope(),
repScope = this.scope,
lastObservedBounds,
broadcastBounds;
// Combine start/end times into a single object
function bounds(start, end) {
return {
start: conductor.displayStart(),
end: conductor.displayEnd(),
domain: conductor.domain()
};
}
function boundsAreStable(newlyObservedBounds) {
return !lastObservedBounds ||
(lastObservedBounds.start === newlyObservedBounds.start &&
lastObservedBounds.end === newlyObservedBounds.end);
}
function updateConductorInner() {
var innerBounds = conductorScope.ngModel.conductor.inner;
conductor.displayStart(innerBounds.start);
conductor.displayEnd(innerBounds.end);
lastObservedBounds = lastObservedBounds || bounds();
broadcastBounds();
}
function updateDomain(value) {
conductor.domain(value);
repScope.$broadcast('telemetry:display:bounds', bounds(
conductor.displayStart(),
conductor.displayEnd(),
conductor.domain()
));
}
// telemetry domain metadata -> option for a select control
function makeOption(domainOption) {
return {
name: domainOption.name,
value: domainOption.key
};
}
broadcastBounds = this.throttle(function () {
var newlyObservedBounds = bounds();
if (boundsAreStable(newlyObservedBounds)) {
repScope.$broadcast('telemetry:display:bounds', bounds());
lastObservedBounds = undefined;
} else {
lastObservedBounds = newlyObservedBounds;
broadcastBounds();
}
}, THROTTLE_MS);
conductorScope.ngModel = {};
conductorScope.ngModel.conductor =
{ outer: bounds(), inner: bounds() };
conductorScope.ngModel.options =
conductor.domainOptions().map(makeOption);
conductorScope.ngModel.domain = conductor.domain();
conductorScope
.$watch('ngModel.conductor.inner.start', updateConductorInner);
conductorScope
.$watch('ngModel.conductor.inner.end', updateConductorInner);
conductorScope
.$watch('ngModel.domain', updateDomain);
repScope.$on('telemetry:view', updateConductorInner);
};
ConductorRepresenter.prototype.conductorScope = function (s) {
return (this.cScope = arguments.length > 0 ? s : this.cScope);
};
// Handle a specific representation of a specific domain object
ConductorRepresenter.prototype.represent = function represent(representation, representedObject) {
this.destroy();
if (this.views.indexOf(representation) !== -1 && !GLOBAL_SHOWING) {
// Track original states
this.originalHeight = this.element.css('height');
this.hadAbs = this.element.hasClass('abs');
// Create a new scope for the conductor
this.conductorScope(this.scope.$new());
this.wireScope();
this.conductorElement =
this.$compile(TEMPLATE)(this.conductorScope());
this.element.after(this.conductorElement[0]);
this.element.addClass('l-controls-visible l-time-controller-visible');
GLOBAL_SHOWING = true;
}
};
// Respond to the destruction of the current representation.
ConductorRepresenter.prototype.destroy = function destroy() {
// We may not have decided to show in the first place,
// so circumvent any unnecessary cleanup
if (!this.conductorElement) {
return;
}
// Restore the original size of the mct-representation
if (!this.hadAbs) {
this.element.removeClass('abs');
}
this.element.css('height', this.originalHeight);
// ...and remove the conductor
if (this.conductorElement) {
this.conductorElement.remove();
this.conductorElement = undefined;
}
// Finally, destroy its scope
if (this.conductorScope()) {
this.conductorScope().$destroy();
this.conductorScope(undefined);
}
GLOBAL_SHOWING = false;
};
return ConductorRepresenter;
}
);

View File

@ -0,0 +1,64 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./TimeConductor'],
function (TimeConductor) {
'use strict';
var ONE_DAY_IN_MS = 1000 * 60 * 60 * 24,
SIX_HOURS_IN_MS = ONE_DAY_IN_MS / 4;
/**
* Provides a single global instance of the time conductor, which
* controls both query ranges and displayed ranges for telemetry
* data.
*
* @constructor
* @memberof platform/features/conductor
* @param {Function} now a function which returns the current time
* as a UNIX timestamp, in milliseconds
*/
function ConductorService(now, domains) {
var initialEnd =
Math.ceil(now() / SIX_HOURS_IN_MS) * SIX_HOURS_IN_MS;
this.conductor = new TimeConductor(
initialEnd - ONE_DAY_IN_MS,
initialEnd,
domains
);
}
/**
* Get the global instance of the time conductor.
* @returns {platform/features/conductor.TimeConductor} the
* time conductor
*/
ConductorService.prototype.getConductor = function () {
return this.conductor;
};
return ConductorService;
}
);

View File

@ -0,0 +1,76 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
function () {
'use strict';
/**
* Decorates the `telemetryService` such that requests are
* mediated by the time conductor.
*
* @constructor
* @memberof platform/features/conductor
* @implements {TelemetryService}
* @param {platform/features/conductor.ConductorService} conductorServe
* the service which exposes the global time conductor
* @param {TelemetryService} telemetryService the decorated service
*/
function ConductorTelemetryDecorator(conductorService, telemetryService) {
this.conductorService = conductorService;
this.telemetryService = telemetryService;
}
ConductorTelemetryDecorator.prototype.amendRequests = function (requests) {
var conductor = this.conductorService.getConductor(),
start = conductor.displayStart(),
end = conductor.displayEnd(),
domain = conductor.domain();
function amendRequest(request) {
request = request || {};
request.start = start;
request.end = end;
request.domain = domain;
return request;
}
return (requests || []).map(amendRequest);
};
ConductorTelemetryDecorator.prototype.requestTelemetry = function (requests) {
var self = this;
return this.telemetryService
.requestTelemetry(this.amendRequests(requests));
};
ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) {
var self = this;
return this.telemetryService
.subscribe(callback, this.amendRequests(requests));
};
return ConductorTelemetryDecorator;
}
);

View File

@ -0,0 +1,103 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
/**
* The time conductor bundle adds a global control to the bottom of the
* outermost viewing area. This controls both the range for time-based
* queries and for time-based displays.
*
* @namespace platform/features/conductor
*/
define(
function () {
'use strict';
/**
* Tracks the current state of the time conductor.
*
* @memberof platform/features/conductor
* @constructor
* @param {number} start the initial start time
* @param {number} end the initial end time
*/
function TimeConductor(start, end, domains) {
this.range = { start: start, end: end };
this.domains = domains;
this.activeDomain = domains[0].key;
}
/**
* Get or set (if called with an argument) the start time for displays.
* @param {number} [value] the start time to set
* @returns {number} the start time
*/
TimeConductor.prototype.displayStart = function (value) {
if (arguments.length > 0) {
this.range.start = value;
}
return this.range.start;
};
/**
* Get or set (if called with an argument) the end time for displays.
* @param {number} [value] the end time to set
* @returns {number} the end time
*/
TimeConductor.prototype.displayEnd = function (value) {
if (arguments.length > 0) {
this.range.end = value;
}
return this.range.end;
};
/**
* Get available domain options which can be used to bound time
* selection.
* @returns {TelemetryDomain[]} available domains
*/
TimeConductor.prototype.domainOptions = function () {
return this.domains;
};
/**
* Get or set (if called with an argument) the active domain.
* @param {string} [key] the key identifying the domain choice
* @returns {TelemetryDomain} the active telemetry domain
*/
TimeConductor.prototype.domain = function (key) {
function matchesKey(domain) {
return domain.key === key;
}
if (arguments.length > 0) {
if (!this.domains.some(matchesKey)) {
throw new Error("Unknown domain " + key);
}
this.activeDomain = key;
}
return this.activeDomain;
};
return TimeConductor;
}
);

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