Merged from master
2
Procfile
@ -1 +1 @@
|
||||
web: node app.js --port $PORT --include example/localstorage
|
||||
web: node app.js --port $PORT
|
||||
|
15
README.md
@ -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
|
||||
|
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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 Angular’s 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 lookandfeel 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. Userfacing 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
|
||||
|
||||
|
@ -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>
|
||||
|
BIN
docs/src/tutorials/images/add-task.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
docs/src/tutorials/images/bar-plot-2.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
docs/src/tutorials/images/bar-plot-3.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
docs/src/tutorials/images/bar-plot-4.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
docs/src/tutorials/images/bar-plot.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
docs/src/tutorials/images/chrome.png
Normal file
After Width: | Height: | Size: 140 KiB |
BIN
docs/src/tutorials/images/remove-task.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/src/tutorials/images/telemetry-1.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/src/tutorials/images/telemetry-2.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
docs/src/tutorials/images/telemetry-3.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
docs/src/tutorials/images/todo-edit.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
docs/src/tutorials/images/todo-list.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
docs/src/tutorials/images/todo-restyled.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
docs/src/tutorials/images/todo-selection.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
docs/src/tutorials/images/todo.png
Normal file
After Width: | Height: | Size: 43 KiB |
3055
docs/src/tutorials/index.md
Normal file
@ -34,6 +34,10 @@
|
||||
{
|
||||
"key": "time",
|
||||
"name": "Time"
|
||||
},
|
||||
{
|
||||
"key": "yesterday",
|
||||
"name": "Yesterday"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
@ -61,4 +65,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
||||
);
|
@ -4,7 +4,11 @@
|
||||
{
|
||||
"implementation": "WatchIndicator.js",
|
||||
"depends": ["$interval", "$rootScope"]
|
||||
},
|
||||
{
|
||||
"implementation": "DigestIndicator.js",
|
||||
"depends": ["$interval", "$rootScope"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
77
example/profiling/src/DigestIndicator.js
Normal 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;
|
||||
|
||||
}
|
||||
);
|
@ -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",
|
||||
|
@ -1,4 +1,9 @@
|
||||
{
|
||||
"configuration": {
|
||||
"paths": {
|
||||
"uuid": "uuid"
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"routes": [
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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}">
|
||||
|
@ -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">+</span>
|
||||
<span class="title-label">Create</span>
|
||||
</div>
|
||||
|
@ -25,7 +25,7 @@
|
||||
* Module defining CreateService. Created by vwoeltje on 11/10/14.
|
||||
*/
|
||||
define(
|
||||
["../../lib/uuid"],
|
||||
["uuid"],
|
||||
function (uuid) {
|
||||
"use strict";
|
||||
|
||||
|
@ -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
|
||||
|
@ -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": [
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
@ -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) {
|
||||
|
@ -10,9 +10,6 @@
|
||||
&.fixed {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
&.scrolling {
|
||||
overflow: auto;
|
||||
}
|
||||
.controls,
|
||||
label,
|
||||
.inline-block {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
@ -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"
|
||||
>
|
||||
|
@ -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">C</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>
|
||||
</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>
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
302
platform/commonUI/general/src/controllers/TimeRangeController.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
||||
|
73
platform/commonUI/general/src/directives/MCTPopup.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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 {
|
||||
|
89
platform/commonUI/general/src/services/Popup.js
Normal 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;
|
||||
}
|
||||
);
|
127
platform/commonUI/general/src/services/PopupService.js
Normal 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;
|
||||
}
|
||||
);
|
||||
|
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -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");
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
136
platform/commonUI/general/test/directives/MCTPopupSpec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
98
platform/commonUI/general/test/services/PopupServiceSpec.js
Normal 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'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
74
platform/commonUI/general/test/services/PopupSpec.js
Normal 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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
);
|
@ -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"
|
||||
]
|
||||
|
@ -45,13 +45,12 @@
|
||||
"implementation": "services/InfoService.js",
|
||||
"depends": [
|
||||
"$compile",
|
||||
"$document",
|
||||
"$window",
|
||||
"$rootScope",
|
||||
"popupService",
|
||||
"agentService"
|
||||
]
|
||||
}
|
||||
],
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "INFO_HOVER_DELAY",
|
||||
@ -66,4 +65,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -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;
|
@ -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%) {
|
||||
|
@ -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);
|
@ -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) {
|
||||
|
@ -66,6 +66,7 @@
|
||||
"depends": [
|
||||
"persistenceService",
|
||||
"$q",
|
||||
"now",
|
||||
"PERSISTENCE_SPACE",
|
||||
"ADDITIONAL_PERSISTENCE_SPACES"
|
||||
]
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -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" }
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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": [
|
||||
|
62
platform/entanglement/src/actions/GoToOriginalAction.js
Normal 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;
|
||||
}
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
||||
|
95
platform/entanglement/test/actions/GoToOriginalActionSpec.js
Normal 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');
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
[
|
||||
"actions/AbstractComposeAction",
|
||||
"actions/CopyAction",
|
||||
"actions/GoToOriginalAction",
|
||||
"actions/LinkAction",
|
||||
"actions/MoveAction",
|
||||
"services/CopyService",
|
||||
"services/LinkService",
|
||||
"services/MoveService",
|
||||
|
9
platform/features/conductor/README.md
Normal 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.
|
46
platform/features/conductor/bundle.json
Normal 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."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -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>
|
201
platform/features/conductor/src/ConductorRepresenter.js
Normal 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;
|
||||
}
|
||||
);
|
||||
|
64
platform/features/conductor/src/ConductorService.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
103
platform/features/conductor/src/TimeConductor.js
Normal 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;
|
||||
}
|
||||
);
|