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`.
|
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
|
# Glossary
|
||||||
|
|
||||||
Certain terms are used throughout Open MCT Web with consistent meanings
|
Certain terms are used throughout Open MCT Web with consistent meanings
|
||||||
|
@ -30,7 +30,8 @@
|
|||||||
var CONSTANTS = {
|
var CONSTANTS = {
|
||||||
DIAGRAM_WIDTH: 800,
|
DIAGRAM_WIDTH: 800,
|
||||||
DIAGRAM_HEIGHT: 500
|
DIAGRAM_HEIGHT: 500
|
||||||
};
|
},
|
||||||
|
TOC_HEAD = "# Table of Contents";
|
||||||
|
|
||||||
GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be defined
|
GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be defined
|
||||||
(function () {
|
(function () {
|
||||||
@ -44,6 +45,7 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define
|
|||||||
split = require("split"),
|
split = require("split"),
|
||||||
stream = require("stream"),
|
stream = require("stream"),
|
||||||
nomnoml = require('nomnoml'),
|
nomnoml = require('nomnoml'),
|
||||||
|
toc = require("markdown-toc"),
|
||||||
Canvas = require('canvas'),
|
Canvas = require('canvas'),
|
||||||
options = require("minimist")(process.argv.slice(2));
|
options = require("minimist")(process.argv.slice(2));
|
||||||
|
|
||||||
@ -110,6 +112,9 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define
|
|||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
transform._flush = function (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("<html><body>\n");
|
||||||
this.push(marked(markdown));
|
this.push(marked(markdown));
|
||||||
this.push("\n</body></html>\n");
|
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) {
|
customRenderer.link = function (href, title, text) {
|
||||||
// ...but only if they look like relative paths
|
// ...but only if they look like relative paths
|
||||||
return (href || "").indexOf(":") === -1 && href[0] !== "/" ?
|
return (href || "").indexOf(":") === -1 && href[0] !== "/" ?
|
||||||
renderer.link(href.replace(/\.md/, ".html"), title, text) :
|
renderer.link(href.replace(/\.md/, ".html"), title, text) :
|
||||||
renderer.link.apply(renderer, arguments);
|
renderer.link.apply(renderer, arguments);
|
||||||
};
|
};
|
||||||
return customRenderer;
|
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) {
|
glob(options['in'] + "/**/*.@(html|css|png)", {}, function (err, files) {
|
||||||
files.forEach(function (file) {
|
files.forEach(function (file) {
|
||||||
var destination = file.replace(options['in'], options.out),
|
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) {
|
mkdirp(destPath, function (err) {
|
||||||
fs.createReadStream(file, { encoding: 'utf8' })
|
fs.createReadStream(file, streamOptions)
|
||||||
.pipe(fs.createWriteStream(destination, {
|
.pipe(fs.createWriteStream(destination, streamOptions));
|
||||||
encoding: 'utf8'
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -35,16 +35,26 @@ in __any of these tiers__.
|
|||||||
* _DOM_: The rendered HTML document, composed from HTML templates which
|
* _DOM_: The rendered HTML document, composed from HTML templates which
|
||||||
have been processed by AngularJS and will be updated by AngularJS
|
have been processed by AngularJS and will be updated by AngularJS
|
||||||
to reflect changes from the presentation layer. User interactions
|
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
|
* [_Presentation layer_](#presentation-layer): The presentation layer
|
||||||
is responsible for updating (and providing information to update)
|
is responsible for updating (and providing information to update)
|
||||||
the displayed state of the application. The presentation layer consists
|
the displayed state of the application. The presentation layer consists
|
||||||
primarily of _controllers_ and _directives_. The presentation layer is
|
primarily of _controllers_ and _directives_. The presentation layer is
|
||||||
concerned with inspecting the information model and preparing it for
|
concerned with inspecting the information model and preparing it for
|
||||||
display.
|
display.
|
||||||
* [_Information model_](#information-model): The information model
|
* [_Information model_](#information-model): Provides a common (within Open MCT
|
||||||
describes the state and behavior of the objects with which the user
|
Web) set of interfaces for dealing with “things” domain objects within the
|
||||||
interacts.
|
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
|
* [_Service infrastructure_](#service-infrastructure): The service
|
||||||
infrastructure is responsible for providing the underlying general
|
infrastructure is responsible for providing the underlying general
|
||||||
functionality needed to support the information model. This includes
|
functionality needed to support the information model. This includes
|
||||||
@ -52,7 +62,9 @@ in __any of these tiers__.
|
|||||||
back-end.
|
back-end.
|
||||||
* _Back-end_: The back-end is out of the scope of Open MCT Web, except
|
* _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
|
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
|
## Application Start-up
|
||||||
|
|
||||||
|
@ -29,8 +29,9 @@
|
|||||||
Sections:
|
Sections:
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="api/">API</a></li>
|
<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="architecture/">Architecture Overview</a></li>
|
||||||
|
<li><a href="guide/">Developer Guide</a></li>
|
||||||
|
<li><a href="tutorials/">Tutorials</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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",
|
"key": "time",
|
||||||
"name": "Time"
|
"name": "Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "yesterday",
|
||||||
|
"name": "Yesterday"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ranges": [
|
"ranges": [
|
||||||
@ -61,4 +65,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,25 +30,25 @@ define(
|
|||||||
YELLOW = 0.5,
|
YELLOW = 0.5,
|
||||||
LIMITS = {
|
LIMITS = {
|
||||||
rh: {
|
rh: {
|
||||||
cssClass: "s-limit-upr-red",
|
cssClass: "s-limit-upr s-limit-red",
|
||||||
low: RED,
|
low: RED,
|
||||||
high: Number.POSITIVE_INFINITY,
|
high: Number.POSITIVE_INFINITY,
|
||||||
name: "Red High"
|
name: "Red High"
|
||||||
},
|
},
|
||||||
rl: {
|
rl: {
|
||||||
cssClass: "s-limit-lwr-red",
|
cssClass: "s-limit-lwr s-limit-red",
|
||||||
high: -RED,
|
high: -RED,
|
||||||
low: Number.NEGATIVE_INFINITY,
|
low: Number.NEGATIVE_INFINITY,
|
||||||
name: "Red Low"
|
name: "Red Low"
|
||||||
},
|
},
|
||||||
yh: {
|
yh: {
|
||||||
cssClass: "s-limit-upr-yellow",
|
cssClass: "s-limit-upr s-limit-yellow",
|
||||||
low: YELLOW,
|
low: YELLOW,
|
||||||
high: RED,
|
high: RED,
|
||||||
name: "Yellow High"
|
name: "Yellow High"
|
||||||
},
|
},
|
||||||
yl: {
|
yl: {
|
||||||
cssClass: "s-limit-lwr-yellow",
|
cssClass: "s-limit-lwr s-limit-yellow",
|
||||||
low: -RED,
|
low: -RED,
|
||||||
high: -YELLOW,
|
high: -YELLOW,
|
||||||
name: "Yellow Low"
|
name: "Yellow Low"
|
||||||
|
@ -25,8 +25,8 @@
|
|||||||
* Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14.
|
* Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14.
|
||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
["./SinewaveTelemetry"],
|
["./SinewaveTelemetrySeries"],
|
||||||
function (SinewaveTelemetry) {
|
function (SinewaveTelemetrySeries) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +45,7 @@ define(
|
|||||||
function generateData(request) {
|
function generateData(request) {
|
||||||
return {
|
return {
|
||||||
key: request.key,
|
key: request.key,
|
||||||
telemetry: new SinewaveTelemetry(request)
|
telemetry: new SinewaveTelemetrySeries(request)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,4 +112,4 @@ define(
|
|||||||
|
|
||||||
return SinewaveTelemetryProvider;
|
return SinewaveTelemetryProvider;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -29,35 +29,47 @@ define(
|
|||||||
function () {
|
function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var firstObservedTime = Date.now();
|
var ONE_DAY = 60 * 60 * 24,
|
||||||
|
firstObservedTime = Math.floor(Date.now() / 1000) - ONE_DAY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function SinewaveTelemetry(request) {
|
function SinewaveTelemetrySeries(request) {
|
||||||
var latestObservedTime = Date.now(),
|
var timeOffset = (request.domain === 'yesterday') ? ONE_DAY : 0,
|
||||||
count = Math.floor((latestObservedTime - firstObservedTime) / 1000),
|
latestTime = Math.floor(Date.now() / 1000) - timeOffset,
|
||||||
period = request.period || 30,
|
firstTime = firstObservedTime - timeOffset,
|
||||||
generatorData = {};
|
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 () {
|
generatorData.getPointCount = function () {
|
||||||
return count;
|
return count - offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
generatorData.getDomainValue = function (i, domain) {
|
generatorData.getDomainValue = function (i, domain) {
|
||||||
return i * 1000 +
|
return (i + offset) * 1000 + firstTime * 1000 -
|
||||||
(domain !== 'delta' ? firstObservedTime : 0);
|
(domain === 'yesterday' ? ONE_DAY : 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
generatorData.getRangeValue = function (i, range) {
|
generatorData.getRangeValue = function (i, range) {
|
||||||
range = range || "sin";
|
range = range || "sin";
|
||||||
return Math[range](i * Math.PI * 2 / period);
|
return Math[range]((i + offset) * Math.PI * 2 / period);
|
||||||
};
|
};
|
||||||
|
|
||||||
return generatorData;
|
return generatorData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SinewaveTelemetry;
|
return SinewaveTelemetrySeries;
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -4,7 +4,11 @@
|
|||||||
{
|
{
|
||||||
"implementation": "WatchIndicator.js",
|
"implementation": "WatchIndicator.js",
|
||||||
"depends": ["$interval", "$rootScope"]
|
"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",
|
"split": "^1.0.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"nomnoml": "^0.0.3",
|
"nomnoml": "^0.0.3",
|
||||||
"canvas": "^1.2.7"
|
"canvas": "^1.2.7",
|
||||||
|
"markdown-toc": "^0.11.7"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node app.js",
|
"start": "node app.js",
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
"configuration": {
|
||||||
|
"paths": {
|
||||||
|
"uuid": "uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
|
@ -42,9 +42,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='object-holder abs vscroll'>
|
<mct-representation key="representation.selected.key"
|
||||||
<mct-representation key="representation.selected.key"
|
mct-object="representation.selected.key && domainObject"
|
||||||
mct-object="representation.selected.key && domainObject">
|
class="abs object-holder">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
</div>
|
|
||||||
</span>
|
</span>
|
||||||
|
@ -28,7 +28,9 @@
|
|||||||
<mct-split-pane class='contents abs' anchor='left'>
|
<mct-split-pane class='contents abs' anchor='left'>
|
||||||
<div class='split-pane-component treeview pane left'>
|
<div class='split-pane-component treeview pane left'>
|
||||||
<div class="holder abs l-mobile">
|
<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>
|
</mct-representation>
|
||||||
<div class='holder search-holder abs'
|
<div class='holder search-holder abs'
|
||||||
ng-class="{active: treeModel.search}">
|
ng-class="{active: treeModel.search}">
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class="menu-element wrapper" ng-controller="ClickAwayController as createController">
|
<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="ui-symbol icon type-icon">+</span>
|
||||||
<span class="title-label">Create</span>
|
<span class="title-label">Create</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
* Module defining CreateService. Created by vwoeltje on 11/10/14.
|
* Module defining CreateService. Created by vwoeltje on 11/10/14.
|
||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
["../../lib/uuid"],
|
["uuid"],
|
||||||
function (uuid) {
|
function (uuid) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
@ -30,12 +30,11 @@
|
|||||||
structure="toolbar.structure"
|
structure="toolbar.structure"
|
||||||
ng-model="toolbar.state">
|
ng-model="toolbar.state">
|
||||||
</mct-toolbar>
|
</mct-toolbar>
|
||||||
<div class='holder abs object-holder work-area'>
|
<mct-representation key="representation.selected.key"
|
||||||
<mct-representation key="representation.selected.key"
|
toolbar="toolbar"
|
||||||
toolbar="toolbar"
|
mct-object="representation.selected.key && domainObject"
|
||||||
mct-object="representation.selected.key && domainObject">
|
class="holder abs object-holder work-area">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<mct-splitter></mct-splitter>
|
<mct-splitter></mct-splitter>
|
||||||
<div
|
<div
|
||||||
|
@ -8,6 +8,11 @@
|
|||||||
"key": "urlService",
|
"key": "urlService",
|
||||||
"implementation": "services/UrlService.js",
|
"implementation": "services/UrlService.js",
|
||||||
"depends": [ "$location" ]
|
"depends": [ "$location" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "popupService",
|
||||||
|
"implementation": "services/PopupService.js",
|
||||||
|
"depends": [ "$document", "$window" ]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"runs": [
|
"runs": [
|
||||||
@ -53,6 +58,16 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"controllers": [
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "TimeRangeController",
|
||||||
|
"implementation": "controllers/TimeRangeController.js",
|
||||||
|
"depends": [ "$scope", "now" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "DateTimePickerController",
|
||||||
|
"implementation": "controllers/DateTimePickerController.js",
|
||||||
|
"depends": [ "$scope", "now" ]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "TreeNodeController",
|
"key": "TreeNodeController",
|
||||||
"implementation": "controllers/TreeNodeController.js",
|
"implementation": "controllers/TreeNodeController.js",
|
||||||
@ -118,11 +133,21 @@
|
|||||||
"implementation": "directives/MCTDrag.js",
|
"implementation": "directives/MCTDrag.js",
|
||||||
"depends": [ "$document" ]
|
"depends": [ "$document" ]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "mctClickElsewhere",
|
||||||
|
"implementation": "directives/MCTClickElsewhere.js",
|
||||||
|
"depends": [ "$document" ]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "mctResize",
|
"key": "mctResize",
|
||||||
"implementation": "directives/MCTResize.js",
|
"implementation": "directives/MCTResize.js",
|
||||||
"depends": [ "$timeout" ]
|
"depends": [ "$timeout" ]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "mctPopup",
|
||||||
|
"implementation": "directives/MCTPopup.js",
|
||||||
|
"depends": [ "$compile", "popupService" ]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "mctScrollX",
|
"key": "mctScrollX",
|
||||||
"implementation": "directives/MCTScroll.js",
|
"implementation": "directives/MCTScroll.js",
|
||||||
@ -226,6 +251,10 @@
|
|||||||
{
|
{
|
||||||
"key": "selector",
|
"key": "selector",
|
||||||
"templateUrl": "templates/controls/selector.html"
|
"templateUrl": "templates/controls/selector.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "datetime-picker",
|
||||||
|
"templateUrl": "templates/controls/datetime-picker.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"licenses": [
|
"licenses": [
|
||||||
|
@ -45,6 +45,7 @@ $ueEditToolBarH: 25px;
|
|||||||
$ueBrowseLeftPaneW: 25%;
|
$ueBrowseLeftPaneW: 25%;
|
||||||
$ueEditLeftPaneW: 75%;
|
$ueEditLeftPaneW: 75%;
|
||||||
$treeSearchInputBarH: 25px;
|
$treeSearchInputBarH: 25px;
|
||||||
|
$ueTimeControlH: (33px, 20px, 20px);
|
||||||
// Overlay
|
// Overlay
|
||||||
$ovrTopBarH: 45px;
|
$ovrTopBarH: 45px;
|
||||||
$ovrFooterH: 24px;
|
$ovrFooterH: 24px;
|
||||||
@ -105,3 +106,8 @@ $dirImgs: $dirCommonRes + 'images/';
|
|||||||
|
|
||||||
/************************** TIMINGS */
|
/************************** TIMINGS */
|
||||||
$controlFadeMs: 100ms;
|
$controlFadeMs: 100ms;
|
||||||
|
|
||||||
|
/************************** LIMITS */
|
||||||
|
$glyphLimit: '\e603';
|
||||||
|
$glyphLimitUpr: '\0000eb';
|
||||||
|
$glyphLimitLwr: '\0000ee';
|
||||||
|
@ -40,11 +40,11 @@
|
|||||||
|
|
||||||
/************************** HTML ENTITIES */
|
/************************** HTML ENTITIES */
|
||||||
a {
|
a {
|
||||||
color: #ccc;
|
color: $colorA;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #fff;
|
color: $colorAHov;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,6 +125,14 @@ mct-container {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scrolling {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscroll {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.no-margin {
|
.no-margin {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ui-symbol {
|
.ui-symbol {
|
||||||
|
&.type-icon {
|
||||||
|
color: $colorObjHdrIc;
|
||||||
|
}
|
||||||
&.icon {
|
&.icon {
|
||||||
color: $colorKey;
|
color: $colorKey;
|
||||||
&.alert {
|
&.alert {
|
||||||
@ -41,6 +44,9 @@
|
|||||||
font-size: 1.65em;
|
font-size: 1.65em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.icon-calendar:after {
|
||||||
|
content: "\e605";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar .ui-symbol {
|
.bar .ui-symbol {
|
||||||
@ -52,7 +58,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-menu .invoke-menu,
|
.s-menu-btn .invoke-menu,
|
||||||
.icon.major .invoke-menu {
|
.icon.major .invoke-menu {
|
||||||
margin-left: $interiorMarginSm;
|
margin-left: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,39 @@
|
|||||||
@mixin limit($bg, $ic, $glyph) {
|
@mixin limitGlyph($iconColor, $glyph: $glyphLimit) {
|
||||||
background: $bg !important;
|
&:before {
|
||||||
//color: $fg !important;
|
color: $iconColor;
|
||||||
&:before {
|
content: $glyph;
|
||||||
//@include pulse(1000ms);
|
font-family: symbolsfont;
|
||||||
color: $ic;
|
font-size: 0.8em;
|
||||||
content: $glyph;
|
display: inline;
|
||||||
}
|
margin-right: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="s-limit"] {
|
.s-limit-red { background: $colorLimitRedBg !important; }
|
||||||
//white-space: nowrap;
|
.s-limit-yellow { background: $colorLimitYellowBg !important; }
|
||||||
&:before {
|
|
||||||
display: inline-block;
|
// Handle limit when applied to a tr
|
||||||
font-family: symbolsfont;
|
tr[class*="s-limit"] {
|
||||||
font-size: 0.75em;
|
&.s-limit-red td:first-child {
|
||||||
font-style: normal !important;
|
@include limitGlyph($colorLimitRedIc);
|
||||||
margin-right: $interiorMarginSm;
|
}
|
||||||
vertical-align: middle;
|
&.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"); };
|
// Handle limit when applied directly to a non-tr element
|
||||||
.s-limit-upr-yellow { @include limit($colorLimitYellowBg, $colorLimitYellowIc, "\0000ed"); };
|
// Assume this is applied to the element that displays the limit value
|
||||||
.s-limit-lwr-yellow { @include limit($colorLimitYellowBg, $colorLimitYellowIc, "\0000ec"); };
|
:not(tr)[class*="s-limit"] {
|
||||||
.s-limit-lwr-red { @include limit($colorLimitRedBg, $colorLimitRedIc, "\0000ee"); };
|
&.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 */
|
/* This doesn't work on an element inside an element with absolute positioning that has height: auto */
|
||||||
//position: relative;
|
//position: relative;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
-webkit-transform: translateY(-50%);
|
@include webkitProp(transform, translateY(-50%));
|
||||||
-ms-transform: translateY(-50%);
|
//-webkit-transform: translateY(-50%);
|
||||||
transform: translateY(-50%);
|
//-ms-transform: translateY(-50%);
|
||||||
|
//transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin verticalCenterBlock($holderH, $itemH) {
|
@mixin verticalCenterBlock($holderH, $itemH) {
|
||||||
@ -391,22 +392,8 @@
|
|||||||
overflow-y: $showBar;
|
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) {
|
@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) {
|
@mixin tmpBorder($c: #ffcc00, $a: 0.75) {
|
||||||
|
@ -10,9 +10,6 @@
|
|||||||
&.fixed {
|
&.fixed {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
&.scrolling {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
.controls,
|
.controls,
|
||||||
label,
|
label,
|
||||||
.inline-block {
|
.inline-block {
|
||||||
|
@ -177,7 +177,7 @@ label.checkbox.custom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-menu label.checkbox.custom {
|
.s-menu-btn label.checkbox.custom {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,49 +349,155 @@ label.checkbox.custom {
|
|||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
$knobH: 100%; //14px;
|
$knobH: 100%; //14px;
|
||||||
$knobW: 12px;
|
|
||||||
$slotH: 50%;
|
|
||||||
.slot {
|
.slot {
|
||||||
// @include border-radius($basicCr * .75);
|
// @include border-radius($basicCr * .75);
|
||||||
@include sliderTrack();
|
//@include sliderTrack();
|
||||||
height: $slotH;
|
|
||||||
width: auto;
|
width: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: ($knobH - $slotH) / 2;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: auto;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
.knob {
|
.knob {
|
||||||
@include btnSubtle();
|
//@include btnSubtle();
|
||||||
@include controlGrippy(rgba(black, 0.3), vertical, 1px, solid);
|
//@include controlGrippy(rgba(black, 0.3), vertical, 1px, solid);
|
||||||
cursor: ew-resize;
|
@include trans-prop-nice-fade(.25s);
|
||||||
|
background-color: $sliderColorKnob;
|
||||||
|
&:hover {
|
||||||
|
background-color: $sliderColorKnobHov;
|
||||||
|
}
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: $knobH;
|
height: $knobH;
|
||||||
width: $knobW;
|
width: $sliderKnobW;
|
||||||
top: 0;
|
top: 0;
|
||||||
auto: 0;
|
auto: 0;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
left: auto;
|
left: auto;
|
||||||
&:before {
|
}
|
||||||
top: 1px;
|
.knob-l {
|
||||||
bottom: 3px;
|
@include border-left-radius($sliderKnobW);
|
||||||
left: ($knobW / 2) - 1;
|
cursor: w-resize;
|
||||||
}
|
}
|
||||||
|
.knob-r {
|
||||||
|
@include border-right-radius($sliderKnobW);
|
||||||
|
cursor: e-resize;
|
||||||
}
|
}
|
||||||
.range {
|
.range {
|
||||||
background: rgba($colorKey, 0.6);
|
@include trans-prop-nice-fade(.25s);
|
||||||
|
background-color: $sliderColorRange;
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0; //$tbOffset;
|
||||||
right: auto;
|
right: auto;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: auto;
|
left: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
width: auto;
|
width: auto;
|
||||||
&:hover {
|
&: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.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
/******************************************************** MENU BUTTONS */
|
/******************************************************** MENU BUTTONS */
|
||||||
.s-menu {
|
.s-menu-btn {
|
||||||
// Formerly .btn-menu
|
// Formerly .btn-menu
|
||||||
@extend .s-btn;
|
@extend .s-btn;
|
||||||
span.l-click-area {
|
span.l-click-area {
|
||||||
@ -62,186 +62,192 @@
|
|||||||
|
|
||||||
/******************************************************** MENUS THEMSELVES */
|
/******************************************************** MENUS THEMSELVES */
|
||||||
.menu-element {
|
.menu-element {
|
||||||
$bg: $colorMenuBg;
|
|
||||||
$fg: $colorMenuFg;
|
|
||||||
$ic: $colorMenuIc;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
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,
|
.s-menu {
|
||||||
.context-menu,
|
@include border-radius($basicCr);
|
||||||
.super-menu {
|
@include containerSubtle($colorMenuBg, $colorMenuFg);
|
||||||
pointer-events: auto;
|
@include boxShdw($shdwMenu);
|
||||||
ul li {
|
@include txtShdw($shdwMenuText);
|
||||||
//padding-left: 25px;
|
padding: $interiorMarginSm 0;
|
||||||
a {
|
}
|
||||||
color: $fg;
|
|
||||||
}
|
|
||||||
.icon {
|
|
||||||
color: $ic;
|
|
||||||
}
|
|
||||||
.type-icon {
|
|
||||||
left: $interiorMargin;
|
|
||||||
}
|
|
||||||
&:hover .icon {
|
|
||||||
//color: lighten($ic, 5%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-menu {
|
.menu {
|
||||||
// Used in search dropdown in tree
|
@extend .s-menu;
|
||||||
@extend .context-menu;
|
display: block;
|
||||||
ul li {
|
position: absolute;
|
||||||
padding-left: 50px;
|
z-index: 10;
|
||||||
.checkbox {
|
ul {
|
||||||
$d: 0.7rem;
|
@include menuUlReset();
|
||||||
position: absolute;
|
li {
|
||||||
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);
|
@include box-sizing(border-box);
|
||||||
&.left {
|
border-top: 1px solid lighten($colorMenuBg, 20%);
|
||||||
//@include test();
|
color: pullForward($colorMenuBg, 60%);
|
||||||
border-right: 1px solid pullForward($colorMenuBg, 10%);
|
line-height: $menuLineH;
|
||||||
left: 0;
|
padding: $interiorMarginSm $interiorMargin * 2 $interiorMarginSm ($interiorMargin * 2) + $treeTypeIconW;
|
||||||
padding-right: $interiorMargin;
|
position: relative;
|
||||||
right: auto;
|
white-space: nowrap;
|
||||||
width: $plw;
|
&:first-child {
|
||||||
overflow-x: hidden;
|
border: none;
|
||||||
overflow-y: auto;
|
}
|
||||||
ul {
|
&:hover {
|
||||||
li {
|
background: $colorMenuHovBg;
|
||||||
@include border-radius($controlCr);
|
color: $colorMenuHovFg;
|
||||||
padding-left: 30px;
|
.icon {
|
||||||
border-top: none;
|
color: $colorMenuHovIc;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.right {
|
.type-icon {
|
||||||
//@include test(red);
|
left: $interiorMargin * 2;
|
||||||
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($bg, 30%);
|
|
||||||
color: $colorCreateMenuText;
|
|
||||||
font-size: 0.8em;
|
|
||||||
line-height: 1.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.context-menu {
|
|
||||||
font-size: 0.80rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-holder {
|
.menu,
|
||||||
pointer-events: none;
|
.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;
|
position: absolute;
|
||||||
height: 200px;
|
|
||||||
width: 170px;
|
|
||||||
z-index: 70;
|
z-index: 70;
|
||||||
.context-menu-wrapper {
|
.context-menu-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.context-menu {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&.go-left .context-menu {
|
&.go-left .context-menu,
|
||||||
|
&.go-left .menu {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
&.go-up .context-menu {
|
&.go-up .context-menu,
|
||||||
|
&.go-up .menu {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.context-menu-holder {
|
||||||
|
pointer-events: none;
|
||||||
|
height: 200px;
|
||||||
|
width: 170px;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-bar.right .menu,
|
.btn-bar.right .menu,
|
||||||
.menus-to-left .menu {
|
.menus-to-left .menu {
|
||||||
left: auto;
|
left: auto;
|
||||||
|
@ -1,72 +1,155 @@
|
|||||||
.l-time-controller {
|
@mixin toiLineHovEffects() {
|
||||||
$inputTxtW: 90px;
|
//@include pulse(.25s);
|
||||||
$knobW: 9px;
|
&:before,
|
||||||
$r1H: 20px;
|
&:after {
|
||||||
$r2H: 30px;
|
background-color: $timeControllerToiLineColorHov;
|
||||||
$r3H: 10px;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
position: relative;
|
.l-time-controller-visible {
|
||||||
margin: $interiorMarginLg 0;
|
|
||||||
min-width: 400px;
|
}
|
||||||
|
|
||||||
|
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-inputs-holder,
|
||||||
.l-time-range-slider {
|
.l-time-range-slider {
|
||||||
font-size: 0.8em;
|
//font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-time-range-inputs-holder,
|
.l-time-range-inputs-holder,
|
||||||
.l-time-range-slider-holder,
|
.l-time-range-slider-holder,
|
||||||
.l-time-range-ticks-holder
|
.l-time-range-ticks-holder
|
||||||
{
|
{
|
||||||
margin-bottom: $interiorMargin;
|
//@include test();
|
||||||
position: relative;
|
@include absPosDefault(0, visible);
|
||||||
|
@include box-sizing(border-box);
|
||||||
|
top: auto;
|
||||||
}
|
}
|
||||||
.l-time-range-slider,
|
.l-time-range-slider,
|
||||||
.l-time-range-ticks {
|
.l-time-range-ticks {
|
||||||
//@include test(red, 0.1);
|
//@include test(red, 0.1);
|
||||||
@include absPosDefault(0, visible);
|
@include absPosDefault(0, visible);
|
||||||
|
left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-time-range-inputs-holder {
|
.l-time-range-inputs-holder {
|
||||||
height: $r1H;
|
//@include test(red);
|
||||||
}
|
height: $r1H; bottom: $r2H + $r3H + ($interiorMarginSm * 2);
|
||||||
|
padding-top: $interiorMargin;
|
||||||
.l-time-range-slider,
|
border-top: 1px solid $colorInteriorBorder;
|
||||||
.l-time-range-ticks {
|
.type-icon {
|
||||||
left: $inputTxtW; right: $inputTxtW;
|
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 {
|
.l-time-range-slider-holder {
|
||||||
height: $r2H;
|
//@include test(green);
|
||||||
|
height: $r2H; bottom: $r3H + ($interiorMarginSm * 1);
|
||||||
.range-holder {
|
.range-holder {
|
||||||
@include box-shadow(none);
|
@include box-shadow(none);
|
||||||
background: none;
|
background: none;
|
||||||
border: 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 {
|
.l-time-range-ticks-holder {
|
||||||
height: $r3H;
|
height: $r3H;
|
||||||
.l-time-range-ticks {
|
.l-time-range-ticks {
|
||||||
border-top: 1px solid $colorInteriorBorder;
|
border-top: 1px solid $colorTick;
|
||||||
.tick {
|
.tick {
|
||||||
background-color: $colorInteriorBorder;
|
background-color: $colorTick;
|
||||||
border:none;
|
border:none;
|
||||||
|
height: 5px;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
|
position: absolute;
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
.l-time-range-tick-label {
|
.l-time-range-tick-label {
|
||||||
color: lighten($colorInteriorBorder, 20%);
|
@include webkitProp(transform, translateX(-50%));
|
||||||
font-size: 0.7em;
|
color: $colorPlotLabelFg;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.9em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: -0.5 * $tickLblW;
|
top: 8px;
|
||||||
text-align: center;
|
white-space: nowrap;
|
||||||
top: $r3H;
|
|
||||||
width: $tickLblW;
|
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,31 +157,47 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.knob {
|
.knob {
|
||||||
width: $knobW;
|
z-index: 2;
|
||||||
.range-value {
|
.range-value {
|
||||||
$w: 75px;
|
//@include test($sliderColorRange);
|
||||||
//@include test();
|
@include trans-prop-nice-fade(.25s);
|
||||||
|
padding: 0 $rangeValOffset;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
height: $r2H;
|
||||||
margin-top: -7px; // Label is 13px high
|
line-height: $r2H;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: $w;
|
|
||||||
}
|
}
|
||||||
&:hover .range-value {
|
&:hover .range-value {
|
||||||
color: $colorKey;
|
color: $sliderColorKnobHov;
|
||||||
}
|
}
|
||||||
&.knob-l {
|
&.knob-l {
|
||||||
margin-left: $knobW / -2;
|
//@include border-bottom-left-radius($knobCr); // MOVED TO _CONTROLS.SCSS
|
||||||
|
margin-left: $knobM;
|
||||||
.range-value {
|
.range-value {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
right: $knobW + $interiorMargin;
|
right: $rangeValOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.knob-r {
|
&.knob-r {
|
||||||
margin-right: $knobW / -2;
|
//@include border-bottom-right-radius($knobCr);
|
||||||
|
margin-right: $knobM;
|
||||||
.range-value {
|
.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
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
@mixin complexFieldHolder($myW) {
|
||||||
|
width: $myW + $interiorMargin;
|
||||||
|
input[type="text"] {
|
||||||
|
width: $myW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.complex.datetime {
|
.complex.datetime {
|
||||||
span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: $interiorMargin;
|
margin-right: $interiorMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
.field-hints,
|
.field-hints,
|
||||||
.fields {
|
.fields {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.field-hints {
|
.field-hints {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
.fields {
|
.fields {
|
||||||
margin-top: $interiorMarginSm 0;
|
margin-top: $interiorMarginSm 0;
|
||||||
padding: $interiorMarginSm 0;
|
padding: $interiorMarginSm 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
$myW: 80px;
|
@include complexFieldHolder(80px);
|
||||||
width: $myW + $interiorMargin;
|
}
|
||||||
input {
|
|
||||||
width: $myW;
|
.time.md {
|
||||||
}
|
@include complexFieldHolder(60px);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.time.sm {
|
.time.sm {
|
||||||
$myW: 40px;
|
@include complexFieldHolder(40px);
|
||||||
width: $myW + $interiorMargin;
|
|
||||||
input {
|
|
||||||
width: $myW;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,10 +21,13 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
.select {
|
.select {
|
||||||
@include btnSubtle($colorSelectBg);
|
@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;
|
padding: 0 $interiorMargin;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
line-height: $formInputH;
|
||||||
select {
|
select {
|
||||||
@include appearance(none);
|
@include appearance(none);
|
||||||
@include box-sizing(border-box);
|
@include box-sizing(border-box);
|
||||||
@ -40,11 +43,8 @@
|
|||||||
}
|
}
|
||||||
&:after {
|
&:after {
|
||||||
@include contextArrow();
|
@include contextArrow();
|
||||||
|
pointer-events: none;
|
||||||
color: rgba($colorSelectFg, percentToDecimal($contrastInvokeMenuPercent));
|
color: rgba($colorSelectFg, percentToDecimal($contrastInvokeMenuPercent));
|
||||||
//content:"v";
|
|
||||||
//display: block;
|
|
||||||
//font-family: 'symbolsfont';
|
|
||||||
//pointer-events: none;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: $interiorMargin; top: 0;
|
right: $interiorMargin; top: 0;
|
||||||
}
|
}
|
||||||
|
@ -19,24 +19,45 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
@-webkit-keyframes rotation {
|
@include keyframes(rotation) {
|
||||||
from {-webkit-transform: rotate(0deg);}
|
0% { transform: rotate(0deg); }
|
||||||
to {-webkit-transform: rotate(359deg);}
|
100% { transform: rotate(359deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@-moz-keyframes rotation {
|
@mixin wait-spinner2($b: 5px, $c: $colorAlt1) {
|
||||||
from {-moz-transform: rotate(0deg);}
|
@include keyframes(rotateCentered) {
|
||||||
to {-moz-transform: rotate(359deg);}
|
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 {
|
@mixin wait-spinner($b: 5px, $c: $colorAlt1) {
|
||||||
from {-o-transform: rotate(0deg);}
|
display: block;
|
||||||
to {-o-transform: rotate(359deg);}
|
position: absolute;
|
||||||
}
|
-webkit-animation: rotation .6s infinite linear;
|
||||||
|
-moz-animation: rotation .6s infinite linear;
|
||||||
@keyframes rotation {
|
-o-animation: rotation .6s infinite linear;
|
||||||
from {transform: rotate(0deg);}
|
animation: rotation .6s infinite linear;
|
||||||
to {transform: rotate(359deg);}
|
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,
|
.t-wait-spinner,
|
||||||
@ -96,4 +117,28 @@
|
|||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
top: 0; left: 0;
|
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 {
|
thead, .thead {
|
||||||
border-bottom: 1px solid $colorTabHeaderBorder;
|
border-bottom: 1px solid $colorTabHeaderBorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.fixed-header) tr th {
|
||||||
|
background-color: $colorTabHeaderBg;
|
||||||
|
}
|
||||||
|
|
||||||
tbody, .tbody {
|
tbody, .tbody {
|
||||||
display: table-row-group;
|
display: table-row-group;
|
||||||
tr, .tr {
|
tr, .tr {
|
||||||
@ -64,7 +69,6 @@ table {
|
|||||||
display: table-cell;
|
display: table-cell;
|
||||||
}
|
}
|
||||||
th, .th {
|
th, .th {
|
||||||
background-color: $colorTabHeaderBg;
|
|
||||||
border-left: 1px solid $colorTabHeaderBorder;
|
border-left: 1px solid $colorTabHeaderBorder;
|
||||||
color: $colorTabHeaderFg;
|
color: $colorTabHeaderFg;
|
||||||
padding: $tabularTdPadLR $tabularTdPadLR;
|
padding: $tabularTdPadLR $tabularTdPadLR;
|
||||||
@ -143,7 +147,7 @@ table {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: $tabularHeaderH;
|
height: $tabularHeaderH;
|
||||||
background: rgba(#fff, 0.15);
|
background-color: $colorTabHeaderBg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tbody, .tbody {
|
tbody, .tbody {
|
||||||
|
@ -89,7 +89,7 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
|
|||||||
.gl-plot-label,
|
.gl-plot-label,
|
||||||
.l-plot-label {
|
.l-plot-label {
|
||||||
// @include test(yellow);
|
// @include test(yellow);
|
||||||
color: lighten($colorBodyFg, 20%);
|
color: $colorPlotLabelFg;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
// text-transform: uppercase;
|
// text-transform: uppercase;
|
||||||
|
@ -214,8 +214,6 @@
|
|||||||
|
|
||||||
.search-scroll {
|
.search-scroll {
|
||||||
order: 3;
|
order: 3;
|
||||||
|
|
||||||
//padding-right: $rightPadding;
|
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
|
||||||
// Adjustable scrolling size
|
// Adjustable scrolling size
|
||||||
@ -227,28 +225,6 @@
|
|||||||
|
|
||||||
.load-icon {
|
.load-icon {
|
||||||
position: relative;
|
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 {
|
.load-more-button {
|
||||||
|
@ -83,7 +83,6 @@ ul.tree {
|
|||||||
.icon {
|
.icon {
|
||||||
&.l-icon-link,
|
&.l-icon-link,
|
||||||
&.l-icon-alert {
|
&.l-icon-alert {
|
||||||
//@include txtShdw($shdwItemTreeIcon);
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
@ -105,26 +104,12 @@ ul.tree {
|
|||||||
@include absPosDefault();
|
@include absPosDefault();
|
||||||
display: block;
|
display: block;
|
||||||
left: $runningItemW + ($interiorMargin * 3);
|
left: $runningItemW + ($interiorMargin * 3);
|
||||||
//right: $treeContextTriggerW + $interiorMargin;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.loading {
|
|
||||||
pointer-events: none;
|
|
||||||
.label {
|
|
||||||
opacity: 0.5;
|
|
||||||
.title-label {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.wait-spinner {
|
|
||||||
margin-left: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background: $colorItemTreeSelectedBg;
|
background: $colorItemTreeSelectedBg;
|
||||||
color: $colorItemTreeSelectedFg;
|
color: $colorItemTreeSelectedFg;
|
||||||
@ -142,9 +127,6 @@ ul.tree {
|
|||||||
&:hover {
|
&:hover {
|
||||||
background: rgba($colorBodyFg, 0.1); //lighten($colorBodyBg, 5%);
|
background: rgba($colorBodyFg, 0.1); //lighten($colorBodyBg, 5%);
|
||||||
color: pullForward($colorBodyFg, 20%);
|
color: pullForward($colorBodyFg, 20%);
|
||||||
//.context-trigger {
|
|
||||||
// display: block;
|
|
||||||
//}
|
|
||||||
.icon {
|
.icon {
|
||||||
color: $colorItemTreeIconHover;
|
color: $colorItemTreeIconHover;
|
||||||
}
|
}
|
||||||
@ -158,7 +140,6 @@ ul.tree {
|
|||||||
|
|
||||||
.context-trigger {
|
.context-trigger {
|
||||||
$h: 0.9rem;
|
$h: 0.9rem;
|
||||||
//display: none;
|
|
||||||
top: -1px;
|
top: -1px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: $interiorMarginSm;
|
right: $interiorMarginSm;
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
}
|
}
|
||||||
&.frame-template {
|
&.frame-template {
|
||||||
.s-btn,
|
.s-btn,
|
||||||
.s-menu {
|
.s-menu-btn {
|
||||||
height: $ohH;
|
height: $ohH;
|
||||||
line-height: $ohH;
|
line-height: $ohH;
|
||||||
padding: 0 $interiorMargin;
|
padding: 0 $interiorMargin;
|
||||||
@ -56,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-menu:after {
|
.s-menu-btn:after {
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,22 +30,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.holder-all {
|
.holder-all {
|
||||||
$myM: 0; // $interiorMarginSm;
|
$myM: 0; // $interiorMarginSm;
|
||||||
top: $myM;
|
top: $myM;
|
||||||
right: $myM;
|
right: $myM;
|
||||||
bottom: $myM;
|
bottom: $myM;
|
||||||
left: $myM;
|
left: $myM;
|
||||||
}
|
}
|
||||||
|
|
||||||
.browse-area,
|
.browse-area,
|
||||||
.edit-area,
|
.edit-area,
|
||||||
.editor {
|
.editor {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
//.editor {
|
.editor {
|
||||||
// @include border-radius($basicCr * 1.5);
|
@include border-radius($basicCr * 1.5);
|
||||||
//}
|
}
|
||||||
|
|
||||||
.contents {
|
.contents {
|
||||||
$myM: 0; //$interiorMargin;
|
$myM: 0; //$interiorMargin;
|
||||||
@ -68,8 +68,8 @@
|
|||||||
margin-right: $interiorMargin;
|
margin-right: $interiorMargin;
|
||||||
}
|
}
|
||||||
&.abs {
|
&.abs {
|
||||||
text-wrap: none;
|
text-wrap: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
&.left,
|
&.left,
|
||||||
.left {
|
.left {
|
||||||
width: 45%;
|
width: 45%;
|
||||||
@ -95,67 +95,51 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user-environ {
|
.user-environ {
|
||||||
.browse-area,
|
.browse-area,
|
||||||
.edit-area,
|
.edit-area,
|
||||||
.editor {
|
.editor {
|
||||||
top: $bodyMargin + $ueTopBarH + ($interiorMargin);
|
top: $bodyMargin + $ueTopBarH + ($interiorMargin);
|
||||||
right: $bodyMargin;
|
right: $bodyMargin;
|
||||||
bottom: $ueFooterH + $bodyMargin;
|
bottom: $ueFooterH + $bodyMargin;
|
||||||
left: $bodyMargin;
|
left: $bodyMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.browse-area,
|
.browse-area,
|
||||||
.edit-area {
|
.edit-area {
|
||||||
> .contents {
|
> .contents {
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-area {
|
.edit-area {
|
||||||
$tbH: $btnToolbarH + $interiorMargin;
|
$tbH: $btnToolbarH + $interiorMargin;
|
||||||
top: $bodyMargin + $ueTopBarEditH + ($interiorMargin);
|
top: $bodyMargin + $ueTopBarEditH + ($interiorMargin);
|
||||||
.tool-bar {
|
.tool-bar {
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
height: $tbH;
|
height: $tbH;
|
||||||
line-height: $btnToolbarH;
|
line-height: $btnToolbarH;
|
||||||
}
|
}
|
||||||
.work-area {
|
.work-area {
|
||||||
top: $tbH + $interiorMargin * 2;
|
top: $tbH + $interiorMargin * 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// from _bottom-bar.scss
|
.ue-bottom-bar {
|
||||||
.ue-bottom-bar {
|
//@include absPosDefault($bodyMargin);
|
||||||
@include absPosDefault(0);// New status bar design
|
@include absPosDefault(0); // New status bar design
|
||||||
top: auto;
|
top: auto;
|
||||||
height: $ueFooterH;
|
height: $ueFooterH;
|
||||||
line-height: $ueFooterH - ($interiorMargin * 2);
|
.status-holder {
|
||||||
background: $colorFooterBg;
|
//right: $ueAppLogoW + $bodyMargin; New status bar design
|
||||||
color: lighten($colorBodyBg, 30%);
|
z-index: 1;
|
||||||
font-size: .7rem;
|
}
|
||||||
|
.app-logo {
|
||||||
.status-holder {
|
left: auto;
|
||||||
@include box-sizing(border-box);
|
width: $ueAppLogoW;
|
||||||
@include absPosDefault($interiorMargin);
|
z-index: 2;
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cols {
|
.cols {
|
||||||
@ -241,89 +225,89 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.pane {
|
.pane {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
&.treeview.left {
|
&.treeview.left {
|
||||||
.create-btn-holder {
|
.create-btn-holder {
|
||||||
bottom: auto; top: 0;
|
bottom: auto;
|
||||||
height: $ueTopBarH;
|
top: 0;
|
||||||
.wrapper.menu-element {
|
height: $ueTopBarH;
|
||||||
position: absolute;
|
.wrapper.menu-element {
|
||||||
bottom: $interiorMargin;
|
position: absolute;
|
||||||
}
|
bottom: $interiorMargin;
|
||||||
}
|
}
|
||||||
.search-holder {
|
}
|
||||||
top: $ueTopBarH + $interiorMarginLg;
|
.search-holder {
|
||||||
}
|
top: $ueTopBarH + $interiorMarginLg;
|
||||||
.tree-holder {
|
}
|
||||||
overflow: auto;
|
.tree-holder {
|
||||||
top: $ueTopBarH + $interiorMarginLg + $treeSearchInputBarH + $interiorMargin;
|
overflow: auto;
|
||||||
}
|
top: $ueTopBarH + $interiorMarginLg + $treeSearchInputBarH + $interiorMargin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.items {
|
&.items {
|
||||||
.object-browse-bar {
|
.object-browse-bar {
|
||||||
.left.abs,
|
.left.abs,
|
||||||
.right.abs {
|
.right.abs {
|
||||||
top: auto;
|
top: auto;
|
||||||
}
|
}
|
||||||
//.left.abs {
|
|
||||||
// @include tmpBorder(green);
|
|
||||||
//}
|
|
||||||
//.right.abs {
|
|
||||||
// @include tmpBorder(red);
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
.object-holder {
|
|
||||||
top: $ueTopBarH + $interiorMarginLg;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.object-holder {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.split-layout {
|
.split-layout {
|
||||||
&.horizontal {
|
&.horizontal {
|
||||||
// Slides up and down
|
// Slides up and down
|
||||||
>.pane {
|
> .pane {
|
||||||
// @include test();
|
// @include test();
|
||||||
margin-top: $interiorMargin;
|
margin-top: $interiorMargin;
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.vertical {
|
&.vertical {
|
||||||
// Slides left and right
|
// Slides left and right
|
||||||
>.pane {
|
> .pane {
|
||||||
// @include test();
|
// @include test();
|
||||||
margin-left: $interiorMargin;
|
margin-left: $interiorMargin;
|
||||||
>.holder {
|
> .holder {
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
.holder {
|
.holder {
|
||||||
right: $interiorMarginSm;
|
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,
|
.object-browse-bar .s-btn,
|
||||||
.top-bar .buttons-main .s-btn,
|
.top-bar .buttons-main .s-btn,
|
||||||
.top-bar .s-menu,
|
.top-bar .s-menu-btn,
|
||||||
.tool-bar .s-btn,
|
.tool-bar .s-btn,
|
||||||
.tool-bar .s-menu {
|
.tool-bar .s-menu-btn {
|
||||||
$h: $btnToolbarH;
|
$h: $btnToolbarH;
|
||||||
height: $h;
|
height: $h;
|
||||||
line-height: $h;
|
line-height: $h;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.object-browse-bar,
|
.object-browse-bar,
|
||||||
@ -334,33 +318,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.object-browse-bar {
|
.object-browse-bar {
|
||||||
//@include test(blue);
|
//@include test(blue);
|
||||||
@include absPosDefault(0, visible);
|
@include absPosDefault(0, visible);
|
||||||
@include box-sizing(border-box);
|
@include box-sizing(border-box);
|
||||||
height: $ueTopBarH;
|
height: $ueTopBarH;
|
||||||
line-height: $ueTopBarH;
|
line-height: $ueTopBarH;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
padding-right: $interiorMarginLg * 2;
|
padding-right: $interiorMarginLg * 2;
|
||||||
.l-back {
|
.l-back {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
float: left;
|
float: left;
|
||||||
margin-right: $interiorMarginLg;
|
margin-right: $interiorMarginLg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-flex {
|
.l-flex {
|
||||||
@include webkitVal('display', 'flex');
|
@include webkitVal('display', 'flex');
|
||||||
@include webkitProp('flex-flow', 'row nowrap');
|
@include webkitProp('flex-flow', 'row nowrap');
|
||||||
.left {
|
.left {
|
||||||
//@include test(red);
|
//@include test(red);
|
||||||
@include webkitProp(flex, '1 1 0');
|
@include webkitProp(flex, '1 1 0');
|
||||||
padding-right: $interiorMarginLg;
|
padding-right: $interiorMarginLg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vscroll {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
@ -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">
|
<span ng-controller="ViewSwitcherController">
|
||||||
<div
|
<div
|
||||||
class="view-switcher menu-element s-menu"
|
class="view-switcher menu-element s-menu-btn"
|
||||||
ng-if="view.length > 1"
|
ng-if="view.length > 1"
|
||||||
ng-controller="ClickAwayController as toggle"
|
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:
|
Unless required by applicable law or agreed to in writing, software
|
||||||
The thinking is to divide whatever the current time span is by 5,
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
and assign values accordingly to 5 statically-positioned ticks. So the tick x-position is a static percentage
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
of the total width available, and the labels change dynamically. This is consistent
|
License for the specific language governing permissions and limitations
|
||||||
with our current approach to the time axis of plots.
|
under the License.
|
||||||
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.
|
|
||||||
|
|
||||||
|
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="
|
<span class="l-time-range-inputs-elem lbl">to</span>
|
||||||
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>
|
|
||||||
|
|
||||||
<div class="l-time-controller">
|
<span class="l-time-range-input" ng-controller="ToggleController as t2">
|
||||||
<div class="l-time-range-inputs-holder">
|
<!--<span class="lbl">End</span>-->
|
||||||
Start: <input type="date" />
|
<span class="s-btn l-time-range-input">
|
||||||
End: <input type="date" />
|
<input type="text"
|
||||||
</div>
|
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-holder">
|
||||||
<div class="l-time-range-slider">
|
<div class="l-time-range-slider">
|
||||||
<div class="slider">
|
<div class="slider"
|
||||||
<div class="slot range-holder">
|
mct-resize="spanWidth = bounds.width">
|
||||||
<div class="range" style="left: 0%; right: 30%;"></div>
|
<div class="knob knob-l"
|
||||||
</div>
|
mct-drag-down="startLeftDrag()"
|
||||||
<div class="knob knob-l" style="left: 0%;">
|
mct-drag="leftDrag(delta[0])"
|
||||||
<div class="range-value">05/22 14:46</div>
|
ng-style="{ left: startInnerPct }">
|
||||||
</div>
|
<div class="range-value">{{startInnerText}}</div>
|
||||||
<div class="knob knob-r" style="right: 30%;">
|
</div>
|
||||||
<div class="range-value">07/22 01:21</div>
|
<div class="knob knob-r"
|
||||||
</div>
|
mct-drag-down="startRightDrag()"
|
||||||
</div>
|
mct-drag="rightDrag(delta[0])"
|
||||||
</div>
|
ng-style="{ right: endInnerPct }">
|
||||||
</div>
|
<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-holder">
|
||||||
<div class="l-time-range-ticks">
|
<div class="l-time-range-ticks">
|
||||||
<div
|
<div
|
||||||
ng-repeat="tick in ticks"
|
ng-repeat="tick in ticks"
|
||||||
ng-style="{ left: $index * 25 + '%' }"
|
ng-style="{ left: $index * (100 / (ticks.length - 1)) + '%' }"
|
||||||
class="tick tick-x"
|
class="tick tick-x"
|
||||||
>
|
>
|
||||||
<span class="l-time-range-tick-label">{{tick}}</span>
|
<span class="l-time-range-tick-label">{{tick}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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
|
// Link; start listening for changes to an element's size
|
||||||
function link(scope, element, attrs) {
|
function link(scope, element, attrs) {
|
||||||
var lastBounds,
|
var lastBounds,
|
||||||
|
linking = true,
|
||||||
active = true;
|
active = true;
|
||||||
|
|
||||||
// Determine how long to wait before the next update
|
// Determine how long to wait before the next update
|
||||||
@ -74,7 +75,9 @@ define(
|
|||||||
lastBounds.width !== bounds.width ||
|
lastBounds.width !== bounds.width ||
|
||||||
lastBounds.height !== bounds.height) {
|
lastBounds.height !== bounds.height) {
|
||||||
scope.$eval(attrs.mctResize, { bounds: bounds });
|
scope.$eval(attrs.mctResize, { bounds: bounds });
|
||||||
scope.$apply(); // Trigger a digest
|
if (!linking) { // Avoid apply-in-a-digest
|
||||||
|
scope.$apply();
|
||||||
|
}
|
||||||
lastBounds = bounds;
|
lastBounds = bounds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,6 +104,9 @@ define(
|
|||||||
|
|
||||||
// Handle the initial callback
|
// Handle the initial callback
|
||||||
onInterval();
|
onInterval();
|
||||||
|
|
||||||
|
// Trigger scope.$apply on subsequent changes
|
||||||
|
linking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
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/BottomBarController",
|
||||||
"controllers/ClickAwayController",
|
"controllers/ClickAwayController",
|
||||||
"controllers/ContextMenuController",
|
"controllers/ContextMenuController",
|
||||||
|
"controllers/DateTimePickerController",
|
||||||
"controllers/GetterSetterController",
|
"controllers/GetterSetterController",
|
||||||
"controllers/SelectorController",
|
"controllers/SelectorController",
|
||||||
"controllers/SplitPaneController",
|
"controllers/SplitPaneController",
|
||||||
|
"controllers/TimeRangeController",
|
||||||
"controllers/ToggleController",
|
"controllers/ToggleController",
|
||||||
"controllers/TreeNodeController",
|
"controllers/TreeNodeController",
|
||||||
"controllers/ViewSwitcherController",
|
"controllers/ViewSwitcherController",
|
||||||
|
"directives/MCTClickElsewhere",
|
||||||
"directives/MCTContainer",
|
"directives/MCTContainer",
|
||||||
"directives/MCTDrag",
|
"directives/MCTDrag",
|
||||||
|
"directives/MCTPopup",
|
||||||
"directives/MCTResize",
|
"directives/MCTResize",
|
||||||
"directives/MCTScroll",
|
"directives/MCTScroll",
|
||||||
|
"services/Popup",
|
||||||
|
"services/PopupService",
|
||||||
"services/UrlService",
|
"services/UrlService",
|
||||||
"StyleSheetLoader"
|
"StyleSheetLoader"
|
||||||
]
|
]
|
||||||
|
@ -45,13 +45,12 @@
|
|||||||
"implementation": "services/InfoService.js",
|
"implementation": "services/InfoService.js",
|
||||||
"depends": [
|
"depends": [
|
||||||
"$compile",
|
"$compile",
|
||||||
"$document",
|
|
||||||
"$window",
|
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
|
"popupService",
|
||||||
"agentService"
|
"agentService"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"constants": [
|
"constants": [
|
||||||
{
|
{
|
||||||
"key": "INFO_HOVER_DELAY",
|
"key": "INFO_HOVER_DELAY",
|
||||||
@ -66,4 +65,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,13 +31,19 @@ define({
|
|||||||
BUBBLE_TEMPLATE: "<mct-container key=\"bubble\" " +
|
BUBBLE_TEMPLATE: "<mct-container key=\"bubble\" " +
|
||||||
"bubble-title=\"{{bubbleTitle}}\" " +
|
"bubble-title=\"{{bubbleTitle}}\" " +
|
||||||
"bubble-layout=\"{{bubbleLayout}}\" " +
|
"bubble-layout=\"{{bubbleLayout}}\" " +
|
||||||
"class=\"bubble-container\">" +
|
"class=\"bubble-container\">" +
|
||||||
"<mct-include key=\"bubbleTemplate\" ng-model=\"bubbleModel\">" +
|
"<mct-include key=\"bubbleTemplate\" " +
|
||||||
|
"ng-model=\"bubbleModel\">" +
|
||||||
"</mct-include>" +
|
"</mct-include>" +
|
||||||
"</mct-container>",
|
"</mct-container>",
|
||||||
// Pixel offset for bubble, to align arrow position
|
// Options and classes for bubble
|
||||||
BUBBLE_OFFSET: [ 0, -26 ],
|
BUBBLE_OPTIONS: {
|
||||||
// Max width and margins allowed for bubbles; defined in /platform/commonUI/general/res/sass/_constants.scss
|
offsetX: 0,
|
||||||
BUBBLE_MARGIN_LR: 10,
|
offsetY: -26
|
||||||
BUBBLE_MAX_WIDTH: 300
|
},
|
||||||
|
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";
|
"use strict";
|
||||||
|
|
||||||
var BUBBLE_TEMPLATE = InfoConstants.BUBBLE_TEMPLATE,
|
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.
|
* Displays informative content ("info bubbles") for the user.
|
||||||
* @memberof platform/commonUI/inspect
|
* @memberof platform/commonUI/inspect
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function InfoService($compile, $document, $window, $rootScope, agentService) {
|
function InfoService($compile, $rootScope, popupService, agentService) {
|
||||||
this.$compile = $compile;
|
this.$compile = $compile;
|
||||||
this.$document = $document;
|
|
||||||
this.$window = $window;
|
|
||||||
this.$rootScope = $rootScope;
|
this.$rootScope = $rootScope;
|
||||||
|
this.popupService = popupService;
|
||||||
this.agentService = agentService;
|
this.agentService = agentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,53 +55,47 @@ define(
|
|||||||
*/
|
*/
|
||||||
InfoService.prototype.display = function (templateKey, title, content, position) {
|
InfoService.prototype.display = function (templateKey, title, content, position) {
|
||||||
var $compile = this.$compile,
|
var $compile = this.$compile,
|
||||||
$document = this.$document,
|
|
||||||
$window = this.$window,
|
|
||||||
$rootScope = this.$rootScope,
|
$rootScope = this.$rootScope,
|
||||||
body = $document.find('body'),
|
|
||||||
scope = $rootScope.$new(),
|
scope = $rootScope.$new(),
|
||||||
winDim = [$window.innerWidth, $window.innerHeight],
|
span = $compile('<span></span>')(scope),
|
||||||
bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR + InfoConstants.BUBBLE_MAX_WIDTH,
|
bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR +
|
||||||
goLeft = position[0] > (winDim[0] - bubbleSpaceLR),
|
InfoConstants.BUBBLE_MAX_WIDTH,
|
||||||
goUp = position[1] > (winDim[1] / 2),
|
options,
|
||||||
|
popup,
|
||||||
bubble;
|
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
|
// Pass model & container parameters into the scope
|
||||||
scope.bubbleModel = content;
|
scope.bubbleModel = content;
|
||||||
scope.bubbleTemplate = templateKey;
|
scope.bubbleTemplate = templateKey;
|
||||||
scope.bubbleLayout = (goUp ? 'arw-btm' : 'arw-top') + ' ' +
|
|
||||||
(goLeft ? 'arw-right' : 'arw-left');
|
|
||||||
scope.bubbleTitle = title;
|
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);
|
bubble = $compile(BUBBLE_TEMPLATE)(scope);
|
||||||
|
span.append(bubble);
|
||||||
|
|
||||||
// Position the bubble
|
// Return a function to dismiss the info bubble
|
||||||
bubble.css('position', 'absolute');
|
return function dismiss() {
|
||||||
if (this.agentService.isPhone(navigator.userAgent)) {
|
popup.dismiss();
|
||||||
bubble.css('right', '0px');
|
scope.$destroy();
|
||||||
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();
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,117 +28,85 @@ define(
|
|||||||
|
|
||||||
describe("The info service", function () {
|
describe("The info service", function () {
|
||||||
var mockCompile,
|
var mockCompile,
|
||||||
mockDocument,
|
|
||||||
testWindow,
|
|
||||||
mockRootScope,
|
mockRootScope,
|
||||||
|
mockPopupService,
|
||||||
mockAgentService,
|
mockAgentService,
|
||||||
mockCompiledTemplate,
|
mockScope,
|
||||||
testScope,
|
mockElements,
|
||||||
mockBody,
|
mockPopup,
|
||||||
mockElement,
|
|
||||||
service;
|
service;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockCompile = jasmine.createSpy('$compile');
|
mockCompile = jasmine.createSpy('$compile');
|
||||||
mockDocument = jasmine.createSpyObj('$document', ['find']);
|
|
||||||
testWindow = { innerWidth: 1000, innerHeight: 100 };
|
|
||||||
mockRootScope = jasmine.createSpyObj('$rootScope', ['$new']);
|
mockRootScope = jasmine.createSpyObj('$rootScope', ['$new']);
|
||||||
mockAgentService = jasmine.createSpyObj('agentService', ['isMobile', 'isPhone']);
|
mockAgentService = jasmine.createSpyObj('agentService', ['isMobile', 'isPhone']);
|
||||||
mockCompiledTemplate = jasmine.createSpy('template');
|
mockPopupService = jasmine.createSpyObj(
|
||||||
testScope = {};
|
'popupService',
|
||||||
mockBody = jasmine.createSpyObj('body', ['append']);
|
['display']
|
||||||
mockElement = jasmine.createSpyObj('element', ['css', 'remove']);
|
);
|
||||||
|
mockPopup = jasmine.createSpyObj('popup', [
|
||||||
|
'dismiss',
|
||||||
|
'goesLeft',
|
||||||
|
'goesRight',
|
||||||
|
'goesUp',
|
||||||
|
'goesDown'
|
||||||
|
]);
|
||||||
|
|
||||||
mockDocument.find.andCallFake(function (tag) {
|
mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
|
||||||
return tag === 'body' ? mockBody : undefined;
|
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);
|
mockRootScope.$new.andReturn(mockScope);
|
||||||
mockCompiledTemplate.andReturn(mockElement);
|
|
||||||
mockRootScope.$new.andReturn(testScope);
|
|
||||||
|
|
||||||
service = new InfoService(
|
service = new InfoService(
|
||||||
mockCompile,
|
mockCompile,
|
||||||
mockDocument,
|
|
||||||
testWindow,
|
|
||||||
mockRootScope,
|
mockRootScope,
|
||||||
|
mockPopupService,
|
||||||
mockAgentService
|
mockAgentService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates elements and appends them to the body to display", function () {
|
it("creates elements and displays them as popups", function () {
|
||||||
service.display('', '', {}, [0, 0]);
|
service.display('', '', {}, [123, 456]);
|
||||||
expect(mockBody.append).toHaveBeenCalledWith(mockElement);
|
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||||
|
mockElements[0],
|
||||||
|
[ 123, 456 ],
|
||||||
|
jasmine.any(Object)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("provides a function to remove displayed info bubbles", function () {
|
it("provides a function to remove displayed info bubbles", function () {
|
||||||
var fn = service.display('', '', {}, [0, 0]);
|
var fn = service.display('', '', {}, [0, 0]);
|
||||||
expect(mockElement.remove).not.toHaveBeenCalled();
|
expect(mockPopup.dismiss).not.toHaveBeenCalled();
|
||||||
fn();
|
fn();
|
||||||
expect(mockElement.remove).toHaveBeenCalled();
|
expect(mockPopup.dismiss).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("depending on mouse position", function () {
|
it("when on phone device, positions at bottom", function () {
|
||||||
// Positioning should vary based on quadrant in window,
|
mockAgentService.isPhone.andReturn(true);
|
||||||
// which is 1000 x 100 in this test case.
|
service = new InfoService(
|
||||||
it("displays from the top-left in the top-left quadrant", function () {
|
mockCompile,
|
||||||
service.display('', '', {}, [250, 25]);
|
mockRootScope,
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
mockPopupService,
|
||||||
'left',
|
mockAgentService
|
||||||
(250 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
|
);
|
||||||
);
|
service.display('', '', {}, [123, 456]);
|
||||||
expect(mockElement.css).toHaveBeenCalledWith(
|
expect(mockPopupService.display).toHaveBeenCalledWith(
|
||||||
'top',
|
mockElements[0],
|
||||||
(25 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
|
[ 0, -25 ],
|
||||||
);
|
jasmine.any(Object)
|
||||||
});
|
);
|
||||||
|
|
||||||
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]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -7,12 +7,14 @@ $colorKey: #0099cc;
|
|||||||
$colorKeySelectedBg: #005177;
|
$colorKeySelectedBg: #005177;
|
||||||
$colorKeyFg: #fff;
|
$colorKeyFg: #fff;
|
||||||
$colorInteriorBorder: rgba($colorBodyFg, 0.1);
|
$colorInteriorBorder: rgba($colorBodyFg, 0.1);
|
||||||
|
$colorA: #ccc;
|
||||||
|
$colorAHov: #fff;
|
||||||
$contrastRatioPercent: 7%;
|
$contrastRatioPercent: 7%;
|
||||||
$basicCr: 3px;
|
$basicCr: 3px;
|
||||||
$controlCr: 3px;
|
$controlCr: 3px;
|
||||||
$smallCr: 2px;
|
$smallCr: 2px;
|
||||||
|
|
||||||
// Buttons
|
// Buttons and Controls
|
||||||
$colorBtnBg: pullForward($colorBodyBg, $contrastRatioPercent); //
|
$colorBtnBg: pullForward($colorBodyBg, $contrastRatioPercent); //
|
||||||
$colorBtnFg: $colorBodyFg;
|
$colorBtnFg: $colorBodyFg;
|
||||||
$colorBtnMajorBg: $colorKey;
|
$colorBtnMajorBg: $colorKey;
|
||||||
@ -20,6 +22,18 @@ $colorBtnMajorFg: $colorKeyFg;
|
|||||||
$colorBtnIcon: $colorKey;
|
$colorBtnIcon: $colorKey;
|
||||||
$colorInvokeMenu: #fff;
|
$colorInvokeMenu: #fff;
|
||||||
$contrastInvokeMenuPercent: 20%;
|
$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
|
// General Colors
|
||||||
$colorAlt1: #ffc700;
|
$colorAlt1: #ffc700;
|
||||||
@ -32,6 +46,7 @@ $colorGridLines: rgba(#fff, 0.05);
|
|||||||
$colorInvokeMenu: #fff;
|
$colorInvokeMenu: #fff;
|
||||||
$colorObjHdrTxt: $colorBodyFg;
|
$colorObjHdrTxt: $colorBodyFg;
|
||||||
$colorObjHdrIc: pullForward($colorObjHdrTxt, 20%);
|
$colorObjHdrIc: pullForward($colorObjHdrTxt, 20%);
|
||||||
|
$colorTick: rgba(white, 0.2);
|
||||||
|
|
||||||
// Menu colors
|
// Menu colors
|
||||||
$colorMenuBg: pullForward($colorBodyBg, 23%);
|
$colorMenuBg: pullForward($colorBodyBg, 23%);
|
||||||
@ -111,16 +126,17 @@ $colorItemBgSelected: $colorKey;
|
|||||||
$colorTabBorder: pullForward($colorBodyBg, 10%);
|
$colorTabBorder: pullForward($colorBodyBg, 10%);
|
||||||
$colorTabBodyBg: darken($colorBodyBg, 10%);
|
$colorTabBodyBg: darken($colorBodyBg, 10%);
|
||||||
$colorTabBodyFg: lighten($colorTabBodyBg, 40%);
|
$colorTabBodyFg: lighten($colorTabBodyBg, 40%);
|
||||||
$colorTabHeaderBg: lighten($colorBodyBg, 10%);
|
$colorTabHeaderBg: rgba(white, 0.1); // lighten($colorBodyBg, 10%);
|
||||||
$colorTabHeaderFg: lighten($colorTabHeaderBg, 40%);
|
$colorTabHeaderFg: $colorBodyFg; //lighten($colorTabHeaderBg, 40%);
|
||||||
$colorTabHeaderBorder: $colorBodyBg;
|
$colorTabHeaderBorder: $colorBodyBg;
|
||||||
|
|
||||||
// Plot
|
// Plot
|
||||||
$colorPlotBg: rgba(black, 0.1);
|
$colorPlotBg: rgba(black, 0.1);
|
||||||
$colorPlotFg: $colorBodyFg;
|
$colorPlotFg: $colorBodyFg;
|
||||||
$colorPlotHash: rgba(white, 0.2);
|
$colorPlotHash: $colorTick;
|
||||||
$stylePlotHash: dashed;
|
$stylePlotHash: dashed;
|
||||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||||
|
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
$colorItemTreeIcon: $colorKey;
|
$colorItemTreeIcon: $colorKey;
|
||||||
@ -151,5 +167,16 @@ $colorGrippyInteriorHover: $colorKey;
|
|||||||
// Mobile
|
// Mobile
|
||||||
$colorMobilePaneLeft: darken($colorBodyBg, 5%);
|
$colorMobilePaneLeft: darken($colorBodyBg, 5%);
|
||||||
|
|
||||||
|
// Datetime Picker
|
||||||
|
$colorCalCellHovBg: $colorKey;
|
||||||
|
$colorCalCellHovFg: $colorKeyFg;
|
||||||
|
$colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
||||||
|
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||||
|
$colorCalCellInMonthBg: pushBack($colorMenuBg, 5%);
|
||||||
|
|
||||||
// About Screen
|
// 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) {
|
@mixin containerSubtle($bg: $colorBodyBg, $fg: $colorBodyFg, $hover: false) {
|
||||||
@include containerBase($bg, $fg);
|
@include containerBase($bg, $fg);
|
||||||
@include background-image(linear-gradient(lighten($bg, 5%), $bg));
|
@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 containerSubtle($bg, $fg);
|
||||||
@include btnBase($bg, linear-gradient(lighten($bg, 15%), lighten($bg, 10%)), $fg, $ic);
|
@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%) {
|
@function pullForward($c: $colorBodyBg, $p: 20%) {
|
||||||
|
@ -7,12 +7,14 @@ $colorKey: #0099cc;
|
|||||||
$colorKeySelectedBg: $colorKey;
|
$colorKeySelectedBg: $colorKey;
|
||||||
$colorKeyFg: #fff;
|
$colorKeyFg: #fff;
|
||||||
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||||
|
$colorA: #999;
|
||||||
|
$colorAHov: $colorKey;
|
||||||
$contrastRatioPercent: 40%;
|
$contrastRatioPercent: 40%;
|
||||||
$basicCr: 4px;
|
$basicCr: 4px;
|
||||||
$controlCr: $basicCr;
|
$controlCr: $basicCr;
|
||||||
$smallCr: 3px;
|
$smallCr: 3px;
|
||||||
|
|
||||||
// Buttons
|
// Buttons and Controls
|
||||||
$colorBtnBg: pullForward($colorBodyBg, $contrastRatioPercent);
|
$colorBtnBg: pullForward($colorBodyBg, $contrastRatioPercent);
|
||||||
$colorBtnFg: #fff;
|
$colorBtnFg: #fff;
|
||||||
$colorBtnMajorBg: $colorKey;
|
$colorBtnMajorBg: $colorKey;
|
||||||
@ -20,10 +22,22 @@ $colorBtnMajorFg: $colorKeyFg;
|
|||||||
$colorBtnIcon: #eee;
|
$colorBtnIcon: #eee;
|
||||||
$colorInvokeMenu: #000;
|
$colorInvokeMenu: #000;
|
||||||
$contrastInvokeMenuPercent: 40%;
|
$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
|
// General Colors
|
||||||
$colorAlt1: #ff6600;
|
$colorAlt1: #776ba2;
|
||||||
$colorAlert: #ff533a;
|
$colorAlert: #ff3c00;
|
||||||
$colorIconLink: #49dedb;
|
$colorIconLink: #49dedb;
|
||||||
$colorPausedBg: #ff9900;
|
$colorPausedBg: #ff9900;
|
||||||
$colorPausedFg: #fff;
|
$colorPausedFg: #fff;
|
||||||
@ -32,6 +46,7 @@ $colorGridLines: rgba(#000, 0.05);
|
|||||||
$colorInvokeMenu: #fff;
|
$colorInvokeMenu: #fff;
|
||||||
$colorObjHdrTxt: $colorBodyFg;
|
$colorObjHdrTxt: $colorBodyFg;
|
||||||
$colorObjHdrIc: pushBack($colorObjHdrTxt, 30%);
|
$colorObjHdrIc: pushBack($colorObjHdrTxt, 30%);
|
||||||
|
$colorTick: rgba(black, 0.2);
|
||||||
|
|
||||||
// Menu colors
|
// Menu colors
|
||||||
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
||||||
@ -57,20 +72,6 @@ $colorInputBg: $colorGenBg;
|
|||||||
$colorInputFg: $colorBodyFg;
|
$colorInputFg: $colorBodyFg;
|
||||||
$colorFormText: pushBack($colorBodyFg, 10%);
|
$colorFormText: pushBack($colorBodyFg, 10%);
|
||||||
$colorInputIcon: pushBack($colorBodyFg, 25%);
|
$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;
|
$colorSelectBg: #ddd;
|
||||||
$colorSelectFg: $colorBodyFg;
|
$colorSelectFg: $colorBodyFg;
|
||||||
|
|
||||||
@ -118,9 +119,10 @@ $colorTabHeaderBorder: $colorBodyBg;
|
|||||||
// Plot
|
// Plot
|
||||||
$colorPlotBg: rgba(black, 0.05);
|
$colorPlotBg: rgba(black, 0.05);
|
||||||
$colorPlotFg: $colorBodyFg;
|
$colorPlotFg: $colorBodyFg;
|
||||||
$colorPlotHash: rgba(black, 0.2);
|
$colorPlotHash: $colorTick;
|
||||||
$stylePlotHash: dashed;
|
$stylePlotHash: dashed;
|
||||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||||
|
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
$colorItemTreeIcon: $colorKey;
|
$colorItemTreeIcon: $colorKey;
|
||||||
@ -151,5 +153,16 @@ $colorGrippyInteriorHover: $colorBodyBg;
|
|||||||
// Mobile
|
// Mobile
|
||||||
$colorMobilePaneLeft: darken($colorBodyBg, 2%);
|
$colorMobilePaneLeft: darken($colorBodyBg, 2%);
|
||||||
|
|
||||||
|
// Datetime Picker, Calendar
|
||||||
|
$colorCalCellHovBg: $colorKey;
|
||||||
|
$colorCalCellHovFg: $colorKeyFg;
|
||||||
|
$colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
||||||
|
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||||
|
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
|
||||||
|
|
||||||
// About Screen
|
// About Screen
|
||||||
$colorAboutLink: #84b3ff;
|
$colorAboutLink: #84b3ff;
|
||||||
|
|
||||||
|
// Loading
|
||||||
|
$colorLoadingFg: $colorAlt1;
|
||||||
|
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
|
@ -1,5 +1,6 @@
|
|||||||
@mixin containerSubtle($bg: $colorBodyBg, $fg: $colorBodyFg) {
|
@mixin containerSubtle($bg: $colorBodyBg, $fg: $colorBodyFg) {
|
||||||
@include containerBase($bg, $fg);
|
@include containerBase($bg, $fg);
|
||||||
|
@include boxShdw($shdwBtns);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin btnSubtle($bg: $colorBtnBg, $bgHov: none, $fg: $colorBtnFg, $ic: $colorBtnIcon) {
|
@mixin btnSubtle($bg: $colorBtnBg, $bgHov: none, $fg: $colorBtnFg, $ic: $colorBtnIcon) {
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
"depends": [
|
"depends": [
|
||||||
"persistenceService",
|
"persistenceService",
|
||||||
"$q",
|
"$q",
|
||||||
|
"now",
|
||||||
"PERSISTENCE_SPACE",
|
"PERSISTENCE_SPACE",
|
||||||
"ADDITIONAL_PERSISTENCE_SPACES"
|
"ADDITIONAL_PERSISTENCE_SPACES"
|
||||||
]
|
]
|
||||||
|
@ -29,7 +29,8 @@ define(
|
|||||||
function () {
|
function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var TOPIC_PREFIX = "mutation:";
|
var GENERAL_TOPIC = "mutation",
|
||||||
|
TOPIC_PREFIX = "mutation:";
|
||||||
|
|
||||||
// Utility function to overwrite a destination object
|
// Utility function to overwrite a destination object
|
||||||
// with the contents of a source object.
|
// with the contents of a source object.
|
||||||
@ -78,7 +79,11 @@ define(
|
|||||||
* @implements {Capability}
|
* @implements {Capability}
|
||||||
*/
|
*/
|
||||||
function MutationCapability(topic, now, domainObject) {
|
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.now = now;
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
}
|
}
|
||||||
@ -115,11 +120,17 @@ define(
|
|||||||
// mutator function has a temporary copy to work with.
|
// mutator function has a temporary copy to work with.
|
||||||
var domainObject = this.domainObject,
|
var domainObject = this.domainObject,
|
||||||
now = this.now,
|
now = this.now,
|
||||||
t = this.mutationTopic,
|
generalTopic = this.generalMutationTopic,
|
||||||
|
specificTopic = this.specificMutationTopic,
|
||||||
model = domainObject.getModel(),
|
model = domainObject.getModel(),
|
||||||
clone = JSON.parse(JSON.stringify(model)),
|
clone = JSON.parse(JSON.stringify(model)),
|
||||||
useTimestamp = arguments.length > 1;
|
useTimestamp = arguments.length > 1;
|
||||||
|
|
||||||
|
function notifyListeners(model) {
|
||||||
|
generalTopic.notify(domainObject);
|
||||||
|
specificTopic.notify(model);
|
||||||
|
}
|
||||||
|
|
||||||
// Function to handle copying values to the actual
|
// Function to handle copying values to the actual
|
||||||
function handleMutation(mutationResult) {
|
function handleMutation(mutationResult) {
|
||||||
// If mutation result was undefined, just use
|
// If mutation result was undefined, just use
|
||||||
@ -136,7 +147,7 @@ define(
|
|||||||
copyValues(model, result);
|
copyValues(model, result);
|
||||||
}
|
}
|
||||||
model.modified = useTimestamp ? timestamp : now();
|
model.modified = useTimestamp ? timestamp : now();
|
||||||
t.notify(model);
|
notifyListeners(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report the result of the mutation
|
// Report the result of the mutation
|
||||||
@ -158,7 +169,7 @@ define(
|
|||||||
* @memberof platform/core.MutationCapability#
|
* @memberof platform/core.MutationCapability#
|
||||||
*/
|
*/
|
||||||
MutationCapability.prototype.listen = function (listener) {
|
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
|
* @param {PersistenceService} persistenceService the service in which
|
||||||
* domain object models are persisted.
|
* domain object models are persisted.
|
||||||
* @param $q Angular's $q service, for working with promises
|
* @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)
|
* @param {string} space the name of the persistence space(s)
|
||||||
* from which models should be retrieved.
|
* from which models should be retrieved.
|
||||||
* @param {string} spaces additional persistence spaces to use
|
* @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.persistenceService = persistenceService;
|
||||||
this.$q = $q;
|
this.$q = $q;
|
||||||
this.spaces = [space].concat(spaces || []);
|
this.spaces = [space].concat(spaces || []);
|
||||||
|
this.now = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the most recently modified model, for cases where
|
// Take the most recently modified model, for cases where
|
||||||
@ -61,7 +63,9 @@ define(
|
|||||||
PersistedModelProvider.prototype.getModels = function (ids) {
|
PersistedModelProvider.prototype.getModels = function (ids) {
|
||||||
var persistenceService = this.persistenceService,
|
var persistenceService = this.persistenceService,
|
||||||
$q = this.$q,
|
$q = this.$q,
|
||||||
spaces = this.spaces;
|
spaces = this.spaces,
|
||||||
|
space = this.space,
|
||||||
|
now = this.now;
|
||||||
|
|
||||||
// Load a single object model from any persistence spaces
|
// Load a single object model from any persistence spaces
|
||||||
function loadModel(id) {
|
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
|
// Package the result as id->model
|
||||||
function packageResult(models) {
|
function packageResult(models) {
|
||||||
var result = {};
|
var result = {};
|
||||||
ids.forEach(function (id, index) {
|
ids.forEach(function (id, index) {
|
||||||
result[id] = models[index];
|
if (models[index]) {
|
||||||
|
result[id] = addPersistedTimestamp(models[index]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -36,11 +36,16 @@ define(
|
|||||||
*
|
*
|
||||||
* Returns a function that, when invoked, will invoke `fn` after
|
* Returns a function that, when invoked, will invoke `fn` after
|
||||||
* `delay` milliseconds, only if no other invocations are pending.
|
* `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
|
* The returned function will itself return a `Promise` which will
|
||||||
* resolve to the returned value of `fn` whenever that is invoked.
|
* 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}
|
* @returns {Function}
|
||||||
* @memberof platform/core
|
* @memberof platform/core
|
||||||
*/
|
*/
|
||||||
@ -56,12 +61,14 @@ define(
|
|||||||
* @memberof platform/core.Throttle#
|
* @memberof platform/core.Throttle#
|
||||||
*/
|
*/
|
||||||
return function (fn, delay, apply) {
|
return function (fn, delay, apply) {
|
||||||
var activeTimeout;
|
var promise,
|
||||||
|
args = [];
|
||||||
|
|
||||||
// Clear active timeout, so that next invocation starts
|
function invoke() {
|
||||||
// a new one.
|
// Clear the active timeout so a new one starts next time.
|
||||||
function clearActiveTimeout() {
|
promise = undefined;
|
||||||
activeTimeout = undefined;
|
// Invoke the function with the latest supplied arguments.
|
||||||
|
return fn.apply(null, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
@ -69,14 +76,13 @@ define(
|
|||||||
apply = apply || false;
|
apply = apply || false;
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
|
// Store arguments from this invocation
|
||||||
|
args = Array.prototype.slice.apply(arguments, [0]);
|
||||||
// Start a timeout if needed
|
// Start a timeout if needed
|
||||||
if (!activeTimeout) {
|
promise = promise || $timeout(invoke, delay, apply);
|
||||||
activeTimeout = $timeout(fn, delay, apply);
|
|
||||||
activeTimeout.then(clearActiveTimeout);
|
|
||||||
}
|
|
||||||
// Return whichever timeout is active (to get
|
// Return whichever timeout is active (to get
|
||||||
// a promise for the results of fn)
|
// a promise for the results of fn)
|
||||||
return activeTimeout;
|
return promise;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ define(
|
|||||||
SPACE = "space0",
|
SPACE = "space0",
|
||||||
spaces = [ "space1" ],
|
spaces = [ "space1" ],
|
||||||
modTimes,
|
modTimes,
|
||||||
|
mockNow,
|
||||||
provider;
|
provider;
|
||||||
|
|
||||||
function mockPromise(value) {
|
function mockPromise(value) {
|
||||||
@ -55,19 +56,33 @@ define(
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
modTimes = {};
|
modTimes = {};
|
||||||
mockQ = { when: mockPromise, all: mockAll };
|
mockQ = { when: mockPromise, all: mockAll };
|
||||||
mockPersistenceService = {
|
mockPersistenceService = jasmine.createSpyObj(
|
||||||
readObject: function (space, id) {
|
'persistenceService',
|
||||||
|
[
|
||||||
|
'createObject',
|
||||||
|
'readObject',
|
||||||
|
'updateObject',
|
||||||
|
'deleteObject',
|
||||||
|
'listSpaces',
|
||||||
|
'listObjects'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockNow = jasmine.createSpy("now");
|
||||||
|
|
||||||
|
mockPersistenceService.readObject
|
||||||
|
.andCallFake(function (space, id) {
|
||||||
return mockPromise({
|
return mockPromise({
|
||||||
space: space,
|
space: space,
|
||||||
id: id,
|
id: id,
|
||||||
modified: (modTimes[space] || {})[id]
|
modified: (modTimes[space] || {})[id],
|
||||||
|
persisted: 0
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
provider = new PersistedModelProvider(
|
provider = new PersistedModelProvider(
|
||||||
mockPersistenceService,
|
mockPersistenceService,
|
||||||
mockQ,
|
mockQ,
|
||||||
|
mockNow,
|
||||||
SPACE,
|
SPACE,
|
||||||
spaces
|
spaces
|
||||||
);
|
);
|
||||||
@ -81,12 +96,13 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(models).toEqual({
|
expect(models).toEqual({
|
||||||
a: { space: SPACE, id: "a" },
|
a: { space: SPACE, id: "a", persisted: 0 },
|
||||||
x: { space: SPACE, id: "x" },
|
x: { space: SPACE, id: "x", persisted: 0 },
|
||||||
zz: { space: SPACE, id: "zz" }
|
zz: { space: SPACE, id: "zz", persisted: 0 }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("reads object models from multiple spaces", function () {
|
it("reads object models from multiple spaces", function () {
|
||||||
var models;
|
var models;
|
||||||
|
|
||||||
@ -99,9 +115,36 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(models).toEqual({
|
expect(models).toEqual({
|
||||||
a: { space: SPACE, id: "a" },
|
a: { space: SPACE, id: "a", persisted: 0 },
|
||||||
x: { space: 'space1', id: "x", modified: 12321 },
|
x: { space: 'space1', id: "x", modified: 12321, persisted: 0 },
|
||||||
zz: { space: SPACE, id: "zz" }
|
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
|
// Verify precondition: Not called at throttle-time
|
||||||
expect(mockTimeout).not.toHaveBeenCalled();
|
expect(mockTimeout).not.toHaveBeenCalled();
|
||||||
expect(throttled()).toEqual(mockPromise);
|
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 () {
|
it("schedules only one timeout at a time", function () {
|
||||||
@ -59,10 +61,11 @@ define(
|
|||||||
it("schedules additional invocations after resolution", function () {
|
it("schedules additional invocations after resolution", function () {
|
||||||
var throttled = throttle(mockFn);
|
var throttled = throttle(mockFn);
|
||||||
throttled();
|
throttled();
|
||||||
mockPromise.then.mostRecentCall.args[0](); // Resolve timeout
|
mockTimeout.mostRecentCall.args[0](); // Resolve timeout
|
||||||
throttled();
|
throttled();
|
||||||
mockPromise.then.mostRecentCall.args[0]();
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
throttled();
|
throttled();
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
expect(mockTimeout.calls.length).toEqual(3);
|
expect(mockTimeout.calls.length).toEqual(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -31,6 +31,14 @@
|
|||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
"implementation": "actions/LinkAction.js",
|
"implementation": "actions/LinkAction.js",
|
||||||
"depends": ["locationService", "linkService"]
|
"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": [
|
"components": [
|
||||||
@ -53,7 +61,8 @@
|
|||||||
"key": "location",
|
"key": "location",
|
||||||
"name": "Location Capability",
|
"name": "Location Capability",
|
||||||
"description": "Provides a capability for retrieving the location of an object based upon it's context.",
|
"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": [
|
"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 */
|
/*global define */
|
||||||
|
|
||||||
define(
|
define(
|
||||||
@ -12,11 +34,41 @@ define(
|
|||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function LocationCapability(domainObject) {
|
function LocationCapability($q, $injector, domainObject) {
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
|
this.$q = $q;
|
||||||
|
this.$injector = $injector;
|
||||||
return this;
|
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
|
* Set the primary location (the parent id) of the current domain
|
||||||
* object.
|
* object.
|
||||||
@ -78,10 +130,6 @@ define(
|
|||||||
return !this.isLink();
|
return !this.isLink();
|
||||||
};
|
};
|
||||||
|
|
||||||
function createLocationCapability(domainObject) {
|
return LocationCapability;
|
||||||
return new LocationCapability(domainObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
return createLocationCapability;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
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 */
|
/*global define,describe,it,expect,beforeEach,jasmine */
|
||||||
|
|
||||||
define(
|
define(
|
||||||
@ -7,6 +29,7 @@ define(
|
|||||||
'../ControlledPromise'
|
'../ControlledPromise'
|
||||||
],
|
],
|
||||||
function (LocationCapability, domainObjectFactory, ControlledPromise) {
|
function (LocationCapability, domainObjectFactory, ControlledPromise) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
describe("LocationCapability", function () {
|
describe("LocationCapability", function () {
|
||||||
|
|
||||||
@ -14,13 +37,17 @@ define(
|
|||||||
var locationCapability,
|
var locationCapability,
|
||||||
persistencePromise,
|
persistencePromise,
|
||||||
mutationPromise,
|
mutationPromise,
|
||||||
|
mockQ,
|
||||||
|
mockInjector,
|
||||||
|
mockObjectService,
|
||||||
domainObject;
|
domainObject;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
domainObject = domainObjectFactory({
|
domainObject = domainObjectFactory({
|
||||||
|
id: "testObject",
|
||||||
capabilities: {
|
capabilities: {
|
||||||
context: {
|
context: {
|
||||||
getParent: function() {
|
getParent: function () {
|
||||||
return domainObjectFactory({id: 'root'});
|
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();
|
persistencePromise = new ControlledPromise();
|
||||||
domainObject.capabilities.persistence.persist.andReturn(
|
domainObject.capabilities.persistence.persist.andReturn(
|
||||||
persistencePromise
|
persistencePromise
|
||||||
@ -49,7 +81,11 @@ define(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
locationCapability = new LocationCapability(domainObject);
|
locationCapability = new LocationCapability(
|
||||||
|
mockQ,
|
||||||
|
mockInjector,
|
||||||
|
domainObject
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns contextual location", function () {
|
it("returns contextual location", function () {
|
||||||
@ -88,6 +124,57 @@ define(
|
|||||||
expect(whenComplete).toHaveBeenCalled();
|
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/AbstractComposeAction",
|
||||||
"actions/CopyAction",
|
"actions/CopyAction",
|
||||||
|
"actions/GoToOriginalAction",
|
||||||
|
"actions/LinkAction",
|
||||||
|
"actions/MoveAction",
|
||||||
"services/CopyService",
|
"services/CopyService",
|
||||||
"services/LinkService",
|
"services/LinkService",
|
||||||
"services/MoveService",
|
"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;
|
||||||
|
}
|
||||||
|
);
|