Compare commits

..

15 Commits

Author SHA1 Message Date
Pete Richards
d475d767d5 add grootprovider 2016-06-17 17:05:05 -07:00
Pete Richards
a63e053399 [ObjectAPI] Draft new Object API
Rought prototype of new object API.
2016-06-17 16:59:35 -07:00
Victor Woeltjen
5de7a96ccc Merge pull request #1010 from nasa/api-type-proto
[API Prototype] Type registration
2016-06-17 10:18:42 -07:00
Victor Woeltjen
09a833f524 Merge branch 'api-tutorials' into api-type-proto 2016-06-10 13:28:09 -07:00
Victor Woeltjen
9c4e17bfab [Tutorials] Add telemetry tutorial 2016-06-07 13:14:36 -07:00
Victor Woeltjen
d3e5d95d6b [Tutorials] Add example server 2016-06-07 13:00:38 -07:00
Victor Woeltjen
c70793ac2d [Tutorials] Add remainder of bargraph 2016-06-07 12:55:29 -07:00
Victor Woeltjen
a6ef1d3423 [Tutorials] Add Bar Graph tutorial 2016-06-07 12:49:38 -07:00
Victor Woeltjen
c4fec1af6a [API] Move type toward a newer API 2016-05-27 13:31:30 -07:00
Victor Woeltjen
a6996df3df [API] Begin moving out type 2016-05-27 13:17:16 -07:00
Victor Woeltjen
0c660238f2 [API] Add MCT class 2016-05-27 11:49:43 -07:00
Victor Woeltjen
b73b824e55 [API] Add EventEmitter dep 2016-05-27 11:45:59 -07:00
Victor Woeltjen
1954d98628 [Tutorials] Remove diff markings in TodoController 2016-05-27 11:30:53 -07:00
Victor Woeltjen
7aa034ce23 Add todo tutorial 2016-05-26 16:05:38 -07:00
Victor Woeltjen
385dc5d298 Begin adding tutorials 2016-05-26 15:36:09 -07:00
141 changed files with 4208 additions and 5668 deletions

View File

@@ -1,4 +1,4 @@
# Open MCT [![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
# Open MCT
Open MCT is a web-based platform for mission operations user interface
software.
@@ -7,7 +7,7 @@ software.
A bundle is a group of software components (including source code, declared
as AMD modules, as well as resources such as images and HTML templates)
that is intended to be added or removed as a single unit. A plug-in for
that are intended to be added or removed as a single unit. A plug-in for
Open MCT will be expressed as a bundle; platform components are also
expressed as bundles.
@@ -133,6 +133,6 @@ documentation, may presume an understanding of these terms.
it, and it is thereafter considered the _navigated_ object (until the
user makes another such choice.)
* _space_: A name used to identify a persistence store. Interactions with
persistence will generally involve a `space` parameter in some form, to
persistence with generally involve a `space` parameter in some form, to
distinguish multiple persistence stores from one another (for cases
where there are multiple valid persistence locations available.)

8
app.js
View File

@@ -75,8 +75,6 @@
// Expose everything else as static files
app.use(express['static'](options.directory));
// Finally, open the HTTP server and log the instance to the console
app.listen(options.port, function() {
console.log('Open MCT application running at localhost:' + options.port)
});
}());
// Finally, open the HTTP server
app.listen(options.port);
}());

View File

@@ -20,6 +20,6 @@
"FileSaver.js": "^0.0.2",
"zepto": "^1.1.6",
"eventemitter3": "^1.2.0",
"d3": "~4.1.0"
"lodash": "3.10.1"
}
}

View File

@@ -31,10 +31,17 @@
<script type="text/javascript">
require(['main'], function (mct) {
require([
'./tutorials/grootprovider/groots',
'./tutorials/todo/todo',
'./tutorials/todo/bundle',
'./example/imagery/bundle',
'./example/eventGenerator/bundle',
'./example/generator/bundle'
], mct.run.bind(mct));
'./example/generator/bundle',
], function (grootify, todoPlugin) {
grootify(mct);
todoPlugin(mct);
mct.start();
})
});
</script>
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">

24
main.js
View File

@@ -36,7 +36,7 @@ requirejs.config({
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
"zepto": "bower_components/zepto/zepto.min",
"d3": "bower_components/d3/d3.min"
"lodash": "bower_components/lodash/lodash"
},
"shim": {
"angular": {
@@ -56,9 +56,6 @@ requirejs.config({
},
"zepto": {
"exports": "Zepto"
},
"d3": {
"exports": "d3"
}
}
});
@@ -66,6 +63,7 @@ requirejs.config({
define([
'./platform/framework/src/Main',
'legacyRegistry',
'./src/MCT',
'./platform/framework/bundle',
'./platform/core/bundle',
@@ -90,7 +88,6 @@ define([
'./platform/features/pages/bundle',
'./platform/features/plot/bundle',
'./platform/features/timeline/bundle',
'./platform/features/conductor-v2/bundle',
'./platform/features/table/bundle',
'./platform/forms/bundle',
'./platform/identity/bundle',
@@ -102,11 +99,14 @@ define([
'./platform/search/bundle',
'./platform/status/bundle',
'./platform/commonUI/regions/bundle'
], function (Main, legacyRegistry) {
return {
legacyRegistry: legacyRegistry,
run: function () {
return new Main().run(legacyRegistry);
}
};
], function (Main, legacyRegistry, MCT) {
var mct = new MCT();
mct.legacyRegistry = legacyRegistry;
mct.run = mct.start;
mct.on('start', function () {
return new Main().run(legacyRegistry);
});
return mct;
});

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "0.11.0-SNAPSHOT",
"version": "0.10.2-SNAPSHOT",
"description": "The Open MCT core platform",
"dependencies": {
"express": "^4.13.1",

View File

@@ -27,7 +27,6 @@ define([
"./src/MenuArrowController",
"./src/navigation/NavigationService",
"./src/navigation/NavigateAction",
"./src/navigation/OrphanNavigationHandler",
"./src/windowing/NewTabAction",
"./src/windowing/FullscreenAction",
"./src/windowing/WindowTitler",
@@ -48,7 +47,6 @@ define([
MenuArrowController,
NavigationService,
NavigateAction,
OrphanNavigationHandler,
NewTabAction,
FullscreenAction,
WindowTitler,
@@ -93,9 +91,11 @@ define([
"$scope",
"$route",
"$location",
"$window",
"objectService",
"navigationService",
"urlService",
"policyService",
"DEFAULT_PATH"
]
},
@@ -199,9 +199,7 @@ define([
"implementation": NavigateAction,
"depends": [
"navigationService",
"$q",
"policyService",
"$window"
"$q"
]
},
{
@@ -255,14 +253,6 @@ define([
"$rootScope",
"$document"
]
},
{
"implementation": OrphanNavigationHandler,
"depends": [
"throttle",
"topic",
"navigationService"
]
}
],
"licenses": [

View File

@@ -43,7 +43,7 @@
</mct-representation>
</div>
</div>
<div class="holder l-flex-col flex-elem grows l-object-wrapper l-controls-visible l-time-controller-visible">
<div class="holder l-flex-col flex-elem grows l-object-wrapper">
<div class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
<!-- Toolbar and Save/Cancel buttons -->
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
@@ -59,9 +59,4 @@
</mct-representation>
</div>
</div>
<!-- put time conductor in here? -->
<mct-representation mct-object="domainObject"
key="'time-conductor'"
class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder">
</mct-representation>
</div>

View File

@@ -44,9 +44,11 @@ define(
$scope,
$route,
$location,
$window,
objectService,
navigationService,
urlService,
policyService,
defaultPath
) {
var path = [ROOT_ID].concat(
@@ -73,10 +75,25 @@ define(
}
function setScopeObjects(domainObject, navigationAllowed) {
// Callback for updating the in-scope reference to the object
// that is currently navigated-to.
function setNavigation(domainObject) {
var navigationAllowed = true;
if (domainObject === $scope.navigatedObject) {
//do nothing;
return;
}
policyService.allow("navigation", $scope.navigatedObject, domainObject, function (message) {
navigationAllowed = $window.confirm(message + "\r\n\r\n" +
" Are you sure you want to continue?");
});
if (navigationAllowed) {
$scope.navigatedObject = domainObject;
$scope.treeModel.selectedObject = domainObject;
navigationService.setNavigation(domainObject);
updateRoute(domainObject);
} else {
//If navigation was unsuccessful (ie. blocked), reset
@@ -86,20 +103,6 @@ define(
}
}
// Callback for updating the in-scope reference to the object
// that is currently navigated-to.
function setNavigation(domainObject) {
if (domainObject === $scope.navigatedObject) {
//do nothing;
return;
}
if (domainObject) {
domainObject.getCapability("action").perform("navigate").then(setScopeObjects.bind(undefined, domainObject));
} else {
setScopeObjects(domainObject, true);
}
}
function navigateTo(domainObject) {
// Check if an object has been navigated-to already...

View File

@@ -33,12 +33,10 @@ define(
* @constructor
* @implements {Action}
*/
function NavigateAction(navigationService, $q, policyService, $window, context) {
function NavigateAction(navigationService, $q, context) {
this.domainObject = context.domainObject;
this.$q = $q;
this.navigationService = navigationService;
this.policyService = policyService;
this.$window = $window;
}
/**
@@ -47,20 +45,9 @@ define(
* navigation has been updated
*/
NavigateAction.prototype.perform = function () {
var self = this,
navigationAllowed = true;
function allow() {
self.policyService.allow("navigation", self.navigationService.getNavigation(), self.domainObject, function (message) {
navigationAllowed = self.$window.confirm(message + "\r\n\r\n" +
" Are you sure you want to continue?");
});
return navigationAllowed;
}
// Set navigation, and wrap like a promise
return this.$q.when(
allow() && this.navigationService.setNavigation(this.domainObject)
this.navigationService.setNavigation(this.domainObject)
);
};

View File

@@ -1,75 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* Navigates away from orphan objects whenever they are detected.
*
* An orphan object is an object whose apparent parent does not
* actually contain it. This may occur in certain circumstances, such
* as when persistence succeeds for a newly-created object but fails
* for its parent.
*
* @param throttle the `throttle` service
* @param topic the `topic` service
* @param navigationService the `navigationService`
* @constructor
*/
function OrphanNavigationHandler(throttle, topic, navigationService) {
var throttledCheckNavigation;
function getParent(domainObject) {
var context = domainObject.getCapability('context');
return context.getParent();
}
function isOrphan(domainObject) {
var parent = getParent(domainObject),
composition = parent.getModel().composition,
id = domainObject.getId();
return !composition || (composition.indexOf(id) === -1);
}
function navigateToParent(domainObject) {
var parent = getParent(domainObject);
return parent.getCapability('action').perform('navigate');
}
function checkNavigation() {
var navigatedObject = navigationService.getNavigation();
if (navigatedObject.hasCapability('context') &&
isOrphan(navigatedObject)) {
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
navigateToParent(navigatedObject);
}
}
}
throttledCheckNavigation = throttle(checkNavigation);
navigationService.addListener(throttledCheckNavigation);
topic('mutation').listen(throttledCheckNavigation);
}
return OrphanNavigationHandler;
});

View File

@@ -37,8 +37,9 @@ define(
mockUrlService,
mockDomainObject,
mockNextObject,
mockWindow,
mockPolicyService,
testDefaultRoot,
mockActionCapability,
controller;
function mockPromise(value) {
@@ -54,14 +55,25 @@ define(
mockScope,
mockRoute,
mockLocation,
mockWindow,
mockObjectService,
mockNavigationService,
mockUrlService,
mockPolicyService,
testDefaultRoot
);
}
beforeEach(function () {
mockWindow = jasmine.createSpyObj('$window', [
"confirm"
]);
mockWindow.confirm.andReturn(true);
mockPolicyService = jasmine.createSpyObj('policyService', [
'allow'
]);
testDefaultRoot = "some-root-level-domain-object";
mockScope = jasmine.createSpyObj(
@@ -116,8 +128,6 @@ define(
mockNextObject.getId.andReturn("next");
mockDomainObject.getId.andReturn(testDefaultRoot);
mockActionCapability = jasmine.createSpyObj('actionCapability', ['perform']);
instantiateController();
});
@@ -201,13 +211,8 @@ define(
mockContext.getPath.andReturn(
[mockRootObject, mockDomainObject, mockNextObject]
);
//Return true from navigate action
mockActionCapability.perform.andReturn(mockPromise(true));
mockNextObject.getCapability.andCallFake(function (c) {
return (c === 'context' && mockContext) ||
(c === 'action' && mockActionCapability);
return c === 'context' && mockContext;
});
mockScope.$on.andReturn(mockUnlisten);
// Provide a navigation change
@@ -220,7 +225,6 @@ define(
mockLocation.path.andReturn("/browse/");
mockNavigationService.setNavigation.andReturn(true);
mockActionCapability.perform.andReturn(mockPromise(true));
// Exercise the Angular workaround
mockNavigationService.addListener.mostRecentCall.args[0]();
@@ -239,9 +243,6 @@ define(
mockScope.navigatedObject = mockDomainObject;
mockNavigationService.setNavigation.andReturn(true);
mockActionCapability.perform.andReturn(mockPromise(true));
mockNextObject.getCapability.andReturn(mockActionCapability);
//Simulate a change in selected tree object
mockScope.treeModel = {selectedObject: mockDomainObject};
mockScope.$watch.mostRecentCall.args[1](mockNextObject);
@@ -253,10 +254,11 @@ define(
it("after failed navigation event resets the selected tree" +
" object", function () {
mockScope.navigatedObject = mockDomainObject;
//Return false from navigation action
mockActionCapability.perform.andReturn(mockPromise(false));
mockNextObject.getCapability.andReturn(mockActionCapability);
mockWindow.confirm.andReturn(false);
mockPolicyService.allow.andCallFake(function (category, object, context, callback) {
callback("unsaved changes");
return false;
});
//Simulate a change in selected tree object
mockScope.treeModel = {selectedObject: mockDomainObject};

View File

@@ -31,8 +31,6 @@ define(
var mockNavigationService,
mockQ,
mockDomainObject,
mockPolicyService,
mockWindow,
action;
function mockPromise(value) {
@@ -46,70 +44,25 @@ define(
beforeEach(function () {
mockNavigationService = jasmine.createSpyObj(
"navigationService",
[
"setNavigation",
"getNavigation"
]
["setNavigation"]
);
mockNavigationService.getNavigation.andReturn({});
mockQ = { when: mockPromise };
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getId", "getModel", "getCapability"]
);
mockPolicyService = jasmine.createSpyObj("policyService",
[
"allow"
]);
mockWindow = jasmine.createSpyObj("$window",
[
"confirm"
]);
action = new NavigateAction(
mockNavigationService,
mockQ,
mockPolicyService,
mockWindow,
{ domainObject: mockDomainObject }
);
});
it("invokes the policy service to determine if navigation" +
" allowed", function () {
it("invokes the navigate service when performed", function () {
action.perform();
expect(mockPolicyService.allow)
.toHaveBeenCalledWith("navigation", jasmine.any(Object), jasmine.any(Object), jasmine.any(Function));
});
it("prompts user if policy rejection", function () {
action.perform();
expect(mockPolicyService.allow).toHaveBeenCalled();
mockPolicyService.allow.mostRecentCall.args[3]();
expect(mockWindow.confirm).toHaveBeenCalled();
});
describe("shows a prompt", function () {
beforeEach(function () {
// Ensure the allow callback is called synchronously
mockPolicyService.allow.andCallFake(function () {
return arguments[3]();
});
});
it("does not navigate on prompt rejection", function () {
mockWindow.confirm.andReturn(false);
action.perform();
expect(mockNavigationService.setNavigation)
.not.toHaveBeenCalled();
});
it("does navigate on prompt acceptance", function () {
mockWindow.confirm.andReturn(true);
action.perform();
expect(mockNavigationService.setNavigation)
.toHaveBeenCalled();
});
expect(mockNavigationService.setNavigation)
.toHaveBeenCalledWith(mockDomainObject);
});
it("is only applicable when a domain object is in context", function () {

View File

@@ -1,180 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'../../src/navigation/OrphanNavigationHandler'
], function (OrphanNavigationHandler) {
describe("OrphanNavigationHandler", function () {
var mockTopic,
mockThrottle,
mockMutationTopic,
mockNavigationService,
mockDomainObject,
mockParentObject,
mockContext,
mockActionCapability,
mockEditor,
testParentModel,
testId,
mockThrottledFns;
beforeEach(function () {
testId = 'some-identifier';
mockThrottledFns = [];
testParentModel = {};
mockTopic = jasmine.createSpy('topic');
mockThrottle = jasmine.createSpy('throttle');
mockNavigationService = jasmine.createSpyObj('navigationService', [
'getNavigation',
'addListener'
]);
mockMutationTopic = jasmine.createSpyObj('mutationTopic', [
'listen'
]);
mockDomainObject = jasmine.createSpyObj('domainObject', [
'getId',
'getCapability',
'getModel',
'hasCapability'
]);
mockParentObject = jasmine.createSpyObj('domainObject', [
'getId',
'getCapability',
'getModel',
'hasCapability'
]);
mockContext = jasmine.createSpyObj('context', ['getParent']);
mockActionCapability = jasmine.createSpyObj('action', ['perform']);
mockEditor = jasmine.createSpyObj('editor', ['isEditContextRoot']);
mockThrottle.andCallFake(function (fn) {
var mockThrottledFn =
jasmine.createSpy('throttled-' + mockThrottledFns.length);
mockThrottledFn.andCallFake(fn);
mockThrottledFns.push(mockThrottledFn);
return mockThrottledFn;
});
mockTopic.andCallFake(function (k) {
return k === 'mutation' && mockMutationTopic;
});
mockDomainObject.getId.andReturn(testId);
mockDomainObject.getCapability.andCallFake(function (c) {
return {
context: mockContext,
editor: mockEditor
}[c];
});
mockDomainObject.hasCapability.andCallFake(function (c) {
return !!mockDomainObject.getCapability(c);
});
mockParentObject.getModel.andReturn(testParentModel);
mockParentObject.getCapability.andCallFake(function (c) {
return {
action: mockActionCapability
}[c];
});
mockContext.getParent.andReturn(mockParentObject);
mockNavigationService.getNavigation.andReturn(mockDomainObject);
mockEditor.isEditContextRoot.andReturn(false);
return new OrphanNavigationHandler(
mockThrottle,
mockTopic,
mockNavigationService
);
});
it("listens for mutation with a throttled function", function () {
expect(mockMutationTopic.listen)
.toHaveBeenCalledWith(jasmine.any(Function));
expect(mockThrottledFns.indexOf(
mockMutationTopic.listen.mostRecentCall.args[0]
)).not.toEqual(-1);
});
it("listens for navigation changes with a throttled function", function () {
expect(mockNavigationService.addListener)
.toHaveBeenCalledWith(jasmine.any(Function));
expect(mockThrottledFns.indexOf(
mockNavigationService.addListener.mostRecentCall.args[0]
)).not.toEqual(-1);
});
[false, true].forEach(function (isOrphan) {
var prefix = isOrphan ? "" : "non-";
describe("for " + prefix + "orphan objects", function () {
beforeEach(function () {
testParentModel.composition = isOrphan ? [] : [testId];
});
[false, true].forEach(function (isEditRoot) {
var caseName = isEditRoot ?
"that are being edited" : "that are not being edited";
function itNavigatesAsExpected() {
if (isOrphan && !isEditRoot) {
it("navigates to the parent", function () {
expect(mockActionCapability.perform)
.toHaveBeenCalledWith('navigate');
});
} else {
it("does nothing", function () {
expect(mockActionCapability.perform)
.not.toHaveBeenCalled();
});
}
}
describe(caseName, function () {
beforeEach(function () {
mockEditor.isEditContextRoot.andReturn(isEditRoot);
});
describe("when navigation changes", function () {
beforeEach(function () {
mockNavigationService.addListener.mostRecentCall
.args[0](mockDomainObject);
});
itNavigatesAsExpected();
});
describe("when mutation occurs", function () {
beforeEach(function () {
mockMutationTopic.listen.mostRecentCall
.args[0](mockParentObject);
});
itNavigatesAsExpected();
});
});
});
});
});
});
});

View File

@@ -170,7 +170,7 @@ define([
"navigationService",
"$log"
],
"description": "Edit",
"description": "Edit this object.",
"category": "view-control",
"glyph": "p"
},
@@ -206,9 +206,7 @@ define([
"implementation": SaveAction,
"name": "Save",
"description": "Save changes made to these objects.",
"depends": [
"dialogService"
],
"depends": [],
"priority": "mandatory"
},
{

View File

@@ -36,14 +36,14 @@
</ul>
</div>
<div class="pane right menu-item-description l-flex-col">
<div class="desc-area flex-elem holder ui-symbol icon type-icon">
<div class="pane right menu-item-description">
<div class="desc-area ui-symbol icon type-icon">
{{representation.activeMetadata.glyph}}
</div>
<div class="desc-area flex-elem holder title">
<div class="desc-area title">
{{representation.activeMetadata.name}}
</div>
<div class="desc-area flex-elem holder description">
<div class="desc-area description">
{{representation.activeMetadata.description}}
</div>
</div>

View File

@@ -50,24 +50,18 @@ define(
//If the object existed already, navigate to refresh view
// with previous object state.
if (domainObject.getModel().persisted) {
return domainObject.getCapability("action").perform("navigate");
domainObject.getCapability("action").perform("navigate");
} else {
//If the object was new, and user has cancelled, then
//navigate back to parent because nothing to show.
return domainObject.getCapability("location").getOriginal().then(function (original) {
domainObject.getCapability("location").getOriginal().then(function (original) {
parent = original.getCapability("context").getParent();
parent.getCapability("action").perform("navigate");
});
}
}
function cancel(allowed) {
return allowed && domainObject.getCapability("editor").cancel();
}
//Do navigation first in order to trigger unsaved changes dialog
return returnToBrowse()
.then(cancel);
return this.domainObject.getCapability("editor").cancel()
.then(returnToBrowse);
};
/**

View File

@@ -21,8 +21,8 @@
*****************************************************************************/
define(
['./SaveInProgressDialog'],
function (SaveInProgressDialog) {
[],
function () {
/**
* The "Save" action; the action triggered by clicking Save from
@@ -33,11 +33,9 @@ define(
* @memberof platform/commonUI/edit
*/
function SaveAction(
dialogService,
context
) {
this.domainObject = (context || {}).domainObject;
this.dialogService = dialogService;
}
/**
@@ -48,8 +46,7 @@ define(
* @memberof platform/commonUI/edit.SaveAction#
*/
SaveAction.prototype.perform = function () {
var domainObject = this.domainObject,
dialog = new SaveInProgressDialog(this.dialogService);
var domainObject = this.domainObject;
function resolveWith(object) {
return function () {
@@ -75,17 +72,8 @@ define(
return object;
}
function hideBlockingDialog(object) {
dialog.hide();
return object;
}
dialog.show();
return doSave()
.then(hideBlockingDialog)
.then(returnToBrowse)
.catch(hideBlockingDialog);
//return doSave().then(returnToBrowse);
return doSave().then(returnToBrowse);
};
/**

View File

@@ -21,14 +21,9 @@
*****************************************************************************/
define([
'../creation/CreateWizard',
'./SaveInProgressDialog'
],
function (
CreateWizard,
SaveInProgressDialog
) {
define(
['../creation/CreateWizard'],
function (CreateWizard) {
/**
* The "Save" action; the action triggered by clicking Save from
@@ -110,8 +105,7 @@ define([
SaveAsAction.prototype.save = function () {
var self = this,
domainObject = this.domainObject,
copyService = this.copyService,
dialog = new SaveInProgressDialog(this.dialogService);
copyService = this.copyService;
function doWizardSave(parent) {
var wizard = self.createWizard(parent);
@@ -122,16 +116,6 @@ define([
).then(wizard.populateObjectFromInput.bind(wizard));
}
function showBlockingDialog(object) {
dialog.show();
return object;
}
function hideBlockingDialog(object) {
dialog.hide();
return object;
}
function fetchObject(objectId) {
return self.getObjectService().getObjects([objectId]).then(function (objects) {
return objects[objectId];
@@ -156,22 +140,14 @@ define([
.then(resolveWith(clonedObject));
}
function onFailure() {
hideBlockingDialog();
return false;
}
return getParent(domainObject)
.then(doWizardSave)
.then(showBlockingDialog)
.then(getParent)
.then(cloneIntoParent)
.then(commitEditingAfterClone)
.then(hideBlockingDialog)
.catch(onFailure);
.catch(resolveWith(false));
};
/**
* Check if this action is applicable in a given context.
* This will ensure that a domain object is present in the context,

View File

@@ -1,20 +0,0 @@
define([], function () {
function SaveInProgressDialog(dialogService) {
this.dialogService = dialogService;
}
SaveInProgressDialog.prototype.show = function () {
this.dialogService.showBlockingMessage({
title: "Saving...",
hint: "Do not navigate away from this page or close this browser tab while this message is displayed.",
unknownProgress: true,
severity: "info"
});
};
SaveInProgressDialog.prototype.hide = function () {
this.dialogService.dismiss();
};
return SaveInProgressDialog;
});

View File

@@ -67,17 +67,10 @@ define(
}
function onCancel() {
if (self.domainObject.getModel().persisted !== undefined) {
//Fetch clean model from persistence
return self.persistenceCapability.refresh().then(function (result) {
self.persistPending = false;
return result;
});
} else {
return self.persistenceCapability.refresh().then(function (result) {
self.persistPending = false;
//Model is undefined in persistence, so return undefined.
return self.$q.when(undefined);
}
return result;
});
}
if (this.transactionService.isActive()) {

View File

@@ -24,11 +24,12 @@ define(
["../../src/actions/CancelAction"],
function (CancelAction) {
describe("The Cancel action", function () {
var mockDomainObject,
mockParentObject,
capabilities = {},
parentCapabilities = {},
//TODO: Disabled for NEM Beta
xdescribe("The Cancel action", function () {
var mockLocation,
mockDomainObject,
mockEditorCapability,
mockUrlService,
actionContext,
action;
@@ -41,114 +42,61 @@ define(
}
beforeEach(function () {
mockLocation = jasmine.createSpyObj(
"$location",
["path"]
);
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[
"getCapability",
"hasCapability",
"getModel"
]
["getCapability", "hasCapability"]
);
mockDomainObject.getModel.andReturn({});
mockParentObject = jasmine.createSpyObj(
"parentObject",
[
"getCapability"
]
);
mockParentObject.getCapability.andCallFake(function (name) {
return parentCapabilities[name];
});
capabilities.editor = jasmine.createSpyObj(
mockEditorCapability = jasmine.createSpyObj(
"editor",
["save", "cancel", "isEditContextRoot"]
["save", "cancel"]
);
capabilities.action = jasmine.createSpyObj(
"actionCapability",
[
"perform"
]
);
capabilities.location = jasmine.createSpyObj(
"locationCapability",
[
"getOriginal"
]
);
capabilities.location.getOriginal.andReturn(mockPromise(mockDomainObject));
capabilities.context = jasmine.createSpyObj(
"contextCapability",
[
"getParent"
]
);
capabilities.context.getParent.andReturn(mockParentObject);
parentCapabilities.action = jasmine.createSpyObj(
"actionCapability",
[
"perform"
]
mockUrlService = jasmine.createSpyObj(
"urlService",
["urlForLocation"]
);
actionContext = {
domainObject: mockDomainObject
};
mockDomainObject.getCapability.andCallFake(function (name) {
return capabilities[name];
});
mockDomainObject.hasCapability.andReturn(true);
mockDomainObject.getCapability.andReturn(mockEditorCapability);
mockEditorCapability.cancel.andReturn(mockPromise(true));
mockDomainObject.hasCapability.andCallFake(function (name) {
return !!capabilities[name];
});
capabilities.editor.cancel.andReturn(mockPromise(true));
action = new CancelAction(actionContext);
action = new CancelAction(mockLocation, mockUrlService, actionContext);
});
it("only applies to domain object that is being edited", function () {
capabilities.editor.isEditContextRoot.andReturn(true);
it("only applies to domain object with an editor capability", function () {
expect(CancelAction.appliesTo(actionContext)).toBeTruthy();
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
capabilities.editor.isEditContextRoot.andReturn(false);
expect(CancelAction.appliesTo(actionContext)).toBeFalsy();
mockDomainObject.hasCapability.andReturn(false);
mockDomainObject.getCapability.andReturn(undefined);
expect(CancelAction.appliesTo(actionContext)).toBeFalsy();
});
it("invokes the editor capability's cancel functionality when" +
" performed", function () {
mockDomainObject.getModel.andReturn({persisted: 1});
//Return true from navigate action
capabilities.action.perform.andReturn(mockPromise(true));
it("invokes the editor capability's save functionality when performed", function () {
// Verify precondition
expect(mockEditorCapability.cancel).not.toHaveBeenCalled();
action.perform();
// Should have called cancel
expect(capabilities.editor.cancel).toHaveBeenCalled();
expect(mockEditorCapability.cancel).toHaveBeenCalled();
// Definitely shouldn't call save!
expect(capabilities.editor.save).not.toHaveBeenCalled();
expect(mockEditorCapability.save).not.toHaveBeenCalled();
});
it("navigates to object if existing using navigate action", function () {
mockDomainObject.getModel.andReturn({persisted: 1});
//Return true from navigate action
capabilities.action.perform.andReturn(mockPromise(true));
it("returns to browse when performed", function () {
action.perform();
expect(capabilities.action.perform).toHaveBeenCalledWith("navigate");
});
it("navigates to parent if new using navigate action", function () {
mockDomainObject.getModel.andReturn({persisted: undefined});
action.perform();
expect(parentCapabilities.action.perform).toHaveBeenCalledWith("navigate");
expect(mockLocation.path).toHaveBeenCalledWith(
mockUrlService.urlForLocation("browse", mockDomainObject)
);
});
});
}

View File

@@ -28,7 +28,6 @@ define(
var mockDomainObject,
mockEditorCapability,
actionContext,
dialogService,
mockActionCapability,
capabilities = {},
action;
@@ -37,9 +36,6 @@ define(
return {
then: function (callback) {
return mockPromise(callback(value));
},
catch: function (callback) {
return mockPromise(callback(value));
}
};
}
@@ -68,10 +64,6 @@ define(
actionContext = {
domainObject: mockDomainObject
};
dialogService = jasmine.createSpyObj(
"dialogService",
["showBlockingMessage", "dismiss"]
);
mockDomainObject.hasCapability.andReturn(true);
mockDomainObject.getCapability.andCallFake(function (capability) {
@@ -81,7 +73,8 @@ define(
mockEditorCapability.save.andReturn(mockPromise(true));
mockEditorCapability.isEditContextRoot.andReturn(true);
action = new SaveAction(dialogService, actionContext);
action = new SaveAction(actionContext);
});
it("only applies to domain object with an editor capability", function () {
@@ -111,19 +104,6 @@ define(
expect(mockActionCapability.perform).toHaveBeenCalledWith("navigate");
});
it("shows a dialog while saving", function () {
mockEditorCapability.save.andReturn(new Promise(function () {}));
action.perform();
expect(dialogService.showBlockingMessage).toHaveBeenCalled();
expect(dialogService.dismiss).not.toHaveBeenCalled();
});
it("hides a dialog when saving is complete", function () {
action.perform();
expect(dialogService.showBlockingMessage).toHaveBeenCalled();
expect(dialogService.dismiss).toHaveBeenCalled();
});
});
}
);

View File

@@ -100,9 +100,7 @@ define(
mockDialogService = jasmine.createSpyObj(
"dialogService",
[
"getUserInput",
"showBlockingMessage",
"dismiss"
"getUserInput"
]
);
mockDialogService.getUserInput.andReturn(mockPromise(undefined));
@@ -171,19 +169,6 @@ define(
expect(mockDialogService.getUserInput).toHaveBeenCalled();
});
it("shows a blocking dialog while waiting for save", function () {
mockEditorCapability.save.andReturn(new Promise(function () {}));
action.perform();
expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
expect(mockDialogService.dismiss).not.toHaveBeenCalled();
});
it("hides the blocking dialog after saving", function () {
action.perform();
expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
expect(mockDialogService.dismiss).toHaveBeenCalled();
});
});
}
);

View File

@@ -57,15 +57,6 @@ define(
);
mockPersistence.persist.andReturn(fastPromise());
mockPersistence.refresh.andReturn(fastPromise());
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[
"getModel"
]
);
mockDomainObject.getModel.andReturn({persisted: 1});
capability = new TransactionalPersistenceCapability(mockQ, mockTransactionService, mockPersistence, mockDomainObject);
});
@@ -87,20 +78,6 @@ define(
expect(mockPersistence.refresh).toHaveBeenCalled();
});
it("if transaction is active, cancel call is queued that refreshes model when appropriate", function () {
mockTransactionService.isActive.andReturn(true);
capability.persist();
expect(mockTransactionService.addToTransaction).toHaveBeenCalled();
mockDomainObject.getModel.andReturn({});
mockTransactionService.addToTransaction.mostRecentCall.args[1]();
expect(mockPersistence.refresh).not.toHaveBeenCalled();
mockDomainObject.getModel.andReturn({persisted: 1});
mockTransactionService.addToTransaction.mostRecentCall.args[1]();
expect(mockPersistence.refresh).toHaveBeenCalled();
});
it("persist call is only added to transaction once", function () {
mockTransactionService.isActive.andReturn(true);
capability.persist();

View File

@@ -23,12 +23,10 @@
define([
"./src/FormatProvider",
"./src/UTCTimeFormat",
"./src/DurationFormat",
'legacyRegistry'
], function (
FormatProvider,
UTCTimeFormat,
DurationFormat,
legacyRegistry
) {
@@ -50,10 +48,6 @@ define([
{
"key": "utc",
"implementation": UTCTimeFormat
},
{
"key": "duration",
"implementation": DurationFormat
}
],
"constants": [

View File

@@ -204,7 +204,6 @@ define([
"implementation": TimeRangeController,
"depends": [
"$scope",
"$timeout",
"formatService",
"DEFAULT_TIME_FORMAT",
"now"

View File

@@ -100,6 +100,5 @@
<glyph unicode="&#xe620;" glyph-name="icon-tabular-realtime" d="M896 960h-768c-70.606-0.215-127.785-57.394-128-127.979v-768.021c0.215-70.606 57.394-127.785 127.979-128h768.021c70.606 0.215 127.785 57.394 128 127.979v768.021c-0.215 70.606-57.394 127.785-127.979 128zM448 668l25.060-25.32c7.916-7.922 18.856-12.822 30.94-12.822s23.024 4.9 30.94 12.822l75.5 76.3c29.97 30.338 71.571 49.128 117.56 49.128s87.59-18.79 117.544-49.112l50.456-50.997v-152.2c-24.111 8.83-44.678 22.255-61.542 39.342l-75.518 76.318c-7.916 7.922-18.856 12.822-30.94 12.822s-23.024-4.9-30.94-12.822l-75.5-76.3c-29.971-30.343-71.575-49.137-117.568-49.137-20.084 0-39.331 3.584-57.137 10.146l1.145 151.831zM320 0h-192c-35.26 0.214-63.786 28.74-64 63.98v128.020h256v-192zM320 256h-256v192h256v-192zM320 512h-256v192h256v-192zM640 0h-256v192h256v-192zM448 323.38v174.5c1.88-1.74 3.74-3.5 5.56-5.34l75.5-76.3c7.916-7.922 18.856-12.822 30.94-12.822s23.024 4.9 30.94 12.822l75.5 76.3c29.966 30.333 71.56 49.119 117.542 49.119 43.28 0 82.673-16.644 112.128-43.879l-0.11-174.399c-1.88 1.74-3.74 3.5-5.56 5.34l-75.5 76.3c-7.916 7.922-18.856 12.822-30.94 12.822s-23.024-4.9-30.94-12.822l-75.5-76.3c-29.966-30.333-71.56-49.119-117.542-49.119-43.28 0-82.673 16.644-112.128 43.879zM960 64c-0.214-35.26-28.74-63.786-63.98-64h-192.020v192h256v-128z" />
<glyph unicode="&#xe621;" glyph-name="icon-tabular-lad" d="M896 960h-768c-70.606-0.215-127.785-57.394-128-127.979v-768.021c0.215-70.606 57.394-127.785 127.979-128h768.021c70.606 0.215 127.785 57.394 128 127.979v768.021c-0.215 70.606-57.394 127.785-127.979 128zM64 704h256v-192h-256v192zM64 448h256v-192h-256v192zM128 0c-35.26 0.214-63.786 28.74-64 63.98v128.020h256v-192h-192zM384 0v192h256v-192h-256zM960 64c-0.214-35.26-28.74-63.786-63.98-64h-192.020v192h256v-128zM960 448v-192h-576v192h64v64h-64v192h576v-192h-64v-64h64zM782.32 412.62l-110.32 55.16v172.22c0 17.673-14.327 32-32 32s-32-14.327-32-32v-211.78l145.68-72.84c4.172-2.133 9.1-3.383 14.32-3.383 17.675 0 32.003 14.328 32.003 32.003 0 12.454-7.114 23.247-17.501 28.536z" />
<glyph unicode="&#xe622;" glyph-name="icon-tabular-lad-set" d="M128 192v576c-70.606-0.215-127.785-57.394-128-127.979v-576.021c0.215-70.606 57.394-127.785 127.979-128h576.021c70.606 0.215 127.785 57.394 128 127.979l-576 0.021c-70.606 0.215-127.785 57.394-128 127.979zM896 960h-576c-70.606-0.215-127.785-57.394-128-127.979v-576.021c0.215-70.606 57.394-127.785 127.979-128h576.021c70.606 0.215 127.785 57.394 128 127.979v576.021c-0.215 70.606-57.394 127.785-127.979 128zM256 768h192v-128h-192v128zM256 576h192v-192h-192v192zM320 192c-35.26 0.214-63.786 28.74-64 63.98v64.020h192v-128h-128zM512 192v128h192v-128h-192zM960 256c-0.214-35.26-28.74-63.786-63.98-64h-128.020v128h192v-64zM960 384h-448v384h448v-384zM832 480c0.002 0 0.005 0 0.007 0 17.673 0 32 14.327 32 32 0 14.055-9.062 25.994-21.662 30.293l-74.345 24.767v104.94c0 17.673-14.327 32-32 32s-32-14.327-32-32v-151.060l117.88-39.3c3.018-1.040 6.495-1.64 10.113-1.64 0.003 0 0.005 0 0.008 0z" />
<glyph unicode="&#xe623;" glyph-name="icon-download" d="M832 384v-255.66l-0.34-0.34-639.66 0.34v255.66h-192v-256c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v256h-192zM512 320l448 448h-256v192h-384v-192h-256l448-448z" />
<glyph unicode="&#xe642;" glyph-name="icon-x" d="M384 448l-365.332-365.332c-24.89-24.89-24.89-65.62 0-90.51l37.49-37.49c24.89-24.89 65.62-24.89 90.51 0 0 0 365.332 365.332 365.332 365.332l365.332-365.332c24.89-24.89 65.62-24.89 90.51 0l37.49 37.49c24.89 24.89 24.89 65.62 0 90.51l-365.332 365.332c0 0 365.332 365.332 365.332 365.332 24.89 24.89 24.89 65.62 0 90.51l-37.49 37.49c-24.89 24.89-65.62 24.89-90.51 0 0 0-365.332-365.332-365.332-365.332l-365.332 365.332c-24.89 24.89-65.62 24.89-90.51 0l-37.49-37.49c-24.89-24.89-24.89-65.62 0-90.51 0 0 365.332-365.332 365.332-365.332z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,91 +0,0 @@
@include keyframes(rotation) {
100% { @include transform(rotate(360deg)); }
}
@include keyframes(rotation-centered) {
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@include keyframes(clock-hands) {
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@include keyframes(clock-hands-sticky) {
0% {
@include transform(translate(-50%, -50%) rotate(0deg));
}
7% {
@include transform(translate(-50%, -50%) rotate(0deg));
}
8% {
@include transform(translate(-50%, -50%) rotate(30deg));
}
15% {
@include transform(translate(-50%, -50%) rotate(30deg));
}
16% {
@include transform(translate(-50%, -50%) rotate(60deg));
}
24% {
@include transform(translate(-50%, -50%) rotate(60deg));
}
25% {
@include transform(translate(-50%, -50%) rotate(90deg));
}
32% {
@include transform(translate(-50%, -50%) rotate(90deg));
}
33% {
@include transform(translate(-50%, -50%) rotate(120deg));
}
40% {
@include transform(translate(-50%, -50%) rotate(120deg));
}
41% {
@include transform(translate(-50%, -50%) rotate(150deg));
}
49% {
@include transform(translate(-50%, -50%) rotate(150deg));
}
50% {
@include transform(translate(-50%, -50%) rotate(180deg));
}
57% {
@include transform(translate(-50%, -50%) rotate(180deg));
}
58% {
@include transform(translate(-50%, -50%) rotate(210deg));
}
65% {
@include transform(translate(-50%, -50%) rotate(210deg));
}
66% {
@include transform(translate(-50%, -50%) rotate(240deg));
}
74% {
@include transform(translate(-50%, -50%) rotate(240deg));
}
75% {
@include transform(translate(-50%, -50%) rotate(270deg));
}
82% {
@include transform(translate(-50%, -50%) rotate(270deg));
}
83% {
@include transform(translate(-50%, -50%) rotate(300deg));
}
90% {
@include transform(translate(-50%, -50%) rotate(300deg));
}
91% {
@include transform(translate(-50%, -50%) rotate(330deg));
}
99% {
@include transform(translate(-50%, -50%) rotate(330deg));
}
100% {
@include transform(translate(-50%, -50%) rotate(360deg));
}
}

View File

@@ -108,9 +108,6 @@
&.grows {
@include flex(1 1 auto);
}
&.contents-align-right {
text-align: right;
}
}
.flex-container {
// Apply to wrapping elements, mct-includes, etc.
@@ -124,18 +121,17 @@
.l-flex-row {
@include flex-direction(row);
&.flex-elem { @include flex(1 1 auto); }
> .flex-elem {
.flex-elem {
height: inherit;
line-height: inherit;
min-width: 0;
&.holder:not(:last-child) { margin-right: $interiorMargin; }
}
.flex-container { @include flex-direction(row); }
}
.l-flex-col {
@include flex-direction(column);
> .flex-elem {
.flex-elem {
min-height: 0;
&.holder:not(:last-child) { margin-bottom: $interiorMarginLg; }
}

View File

@@ -48,8 +48,8 @@ $uePaneMiniTabW: 10px;
$uePaneMiniTabCollapsedW: 11px;
$ueEditLeftPaneW: 75%;
$treeSearchInputBarH: 25px;
$ueTimeControlH: (25px, 6px, 20px);
/*************** Panes */
$ueTimeControlH: (33px, 18px, 20px);
// Panes
$ueBrowseLeftPaneTreeMinW: 150px;
$ueBrowseLeftPaneTreeMaxW: 35%;
$ueBrowseLeftPaneTreeW: 25%;
@@ -57,59 +57,48 @@ $ueBrowseRightPaneInspectMinW: 200px;
$ueBrowseRightPaneInspectMaxW: 35%;
$ueBrowseRightPaneInspectW: 20%;
$ueDesktopMinW: 600px;
/*************** Overlay */
// Overlay
$ovrTopBarH: 45px;
$ovrFooterH: 24px;
$overlayMargin: 25px;
/*************** Items */
// Items
$ueBrowseGridItemLg: 200px;
$ueBrowseGridItemTopBarH: 20px;
$ueBrowseGridItemBottomBarH: 30px;
$itemPadLR: 5px;
/*************** Tree */
// Tree
$treeVCW: 10px;
$treeTypeIconH: 1.4em; // was 16px
$treeTypeIconHPx: 16px;
$treeTypeIconW: 18px;
$treeContextTriggerW: 20px;
/*************** Tabular */
// Tabular
$tabularHeaderH: 22px; //18px
$tabularTdPadLR: $itemPadLR;
$tabularTdPadTB: 3px;
/*************** Imagery */
// Imagery
$imageMainControlBarH: 25px;
$imageThumbsD: 120px;
$imageThumbsWrapperH: $imageThumbsD * 1.4;
$imageThumbPad: 1px;
/*************** Ticks */
// Ticks
$ticksH: 25px;
$tickLblVMargin: 3px;
$tickLblH: 15px;
$tickLblW: 50px;
$tickH: $ticksH - $tickLblVMargin - $tickLblH;
$tickW: 1px;
/*************** Plots */
$plotYBarW: 60px;
$plotYLabelMinH: 20px;
$plotYLabelW: 10px;
$plotXBarH: 32px;
$plotLegendH: 20px;
$plotSwatchD: 8px;
// 1: Top, 2: right, 3: bottom, 4: left
$plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH + $interiorMargin, $plotYBarW);
/* min plot height is based on user testing to find minimum useful height */
$plotMinH: 95px;
/*************** Bubbles */
// Bubbles
$bubbleArwSize: 10px;
$bubblePad: $interiorMargin;
$bubbleMinW: 100px;
$bubbleMaxW: 300px;
/*************** Forms */
// Forms
$reqSymbolW: 15px;
$reqSymbolM: $interiorMargin * 2;
$reqSymbolFontSize: 0.7em;
$inputTextP: 3px 5px;
/*************** Wait Spinner Defaults */
// Wait Spinner Defaults
$waitSpinnerD: 32px;
$waitSpinnerTreeD: 20px;
$waitSpinnerBorderW: 5px;
@@ -135,8 +124,6 @@ $dirImgs: $dirCommonRes + 'images/';
/************************** TIMINGS */
$controlFadeMs: 100ms;
$browseToEditAnimMs: 400ms;
$editBorderPulseMs: 500ms;
/************************** LIMITS */
$glyphLimit: '\e603';

View File

@@ -39,20 +39,15 @@
@include pulse($animName: pulse-subtle, $dur: 500ms, $opacity0: 0.7);
}
@mixin animTo($animName, $propName, $propValStart, $propValEnd, $dur: 500ms, $delay: 0) {
@include keyframes($animName) {
from { #{propName}: $propValStart; }
to { #{$propName}: $propValEnd; }
@mixin pulseBorder($c: red, $dur: 500ms, $iteration: infinite, $delay: 0s, $opacity0: 0, $opacity100: 1) {
@include keyframes(pulseBorder) {
0% { border-color: rgba($c, $opacity0); }
100% { border-color: rgba($c, $opacity100); }
}
@include animToParams($animName, $dur: 500ms, $delay: 0)
}
@mixin animToParams($animName, $dur: 500ms, $delay: 0) {
@include animation-name($animName);
@include animation-name(pulseBorder);
@include animation-duration($dur);
@include animation-direction(alternate);
@include animation-iteration-count($iteration);
@include animation-timing-function(ease);
@include animation-delay($delay);
@include animation-fill-mode(both);
@include animation-direction(normal);
@include animation-iteration-count(1);
@include animation-timing-function(ease-in-out);
}
}

View File

@@ -66,7 +66,7 @@ input, textarea {
input[type="text"],
input[type="search"] {
vertical-align: baseline;
padding: $inputTextP;
padding: 3px 5px;
}
h1, h2, h3 {

View File

@@ -66,7 +66,8 @@
}
.menu .type-icon,
.tree-item .type-icon {
.tree-item .type-icon,
.super-menu.menu .type-icon {
position: absolute;
}

View File

@@ -21,7 +21,6 @@
*****************************************************************************/
@import "effects";
@import "global";
@import "animations";
@import "archetypes";
@import "about";
@import "text";
@@ -41,7 +40,7 @@
@import "controls/lists";
@import "controls/menus";
@import "controls/messages";
@import "controls/time-conductor";
@import "controls/time-controller";
@import "mobile/controls/menus";
/********************************* FORMS */

View File

@@ -185,15 +185,21 @@
}
@mixin sliderTrack($bg: $scrollbarTrackColorBg) {
//$b: 1px solid lighten($bg, 30%);
border-radius: 2px;
box-sizing: border-box;
@include boxIncised(0.7);
background-color: $bg;
//border-bottom: $b;
//border-right: $b;
}
@mixin controlGrippy($b, $direction: horizontal, $w: 1px, $style: dotted) {
//&:before {
//@include trans-prop-nice("border-color", 25ms);
content: '';
display: block;
//height: auto;
pointer-events: none;
position: absolute;
z-index: 2;
@@ -299,6 +305,7 @@
border-radius: $controlCr;
box-sizing: border-box;
color: $fg;
display: inline-block;
}
@mixin btnBase($bg: $colorBodyBg, $bgHovColor: none, $fg: $colorBodyFg, $ic: $colorBtnIcon) {
@@ -341,6 +348,7 @@
display: inline-block;
font-family: 'symbolsfont';
margin-left: $interiorMarginSm;
vertical-align: top;
}
@mixin nice-textarea($bg: $colorBodyBg, $fg: $colorBodyFg) {

View File

@@ -33,11 +33,18 @@ $pad: $interiorMargin * $baseRatio;
.s-btn {
box-sizing: border-box;
display: inline-block;
padding: 0 $pad;
font-size: 0.7rem;
vertical-align: top;
@include btnSubtle($colorBtnBg, $colorBtnBgHov, $colorBtnFg, $colorBtnIcon);
.icon {
font-size: 0.8rem;
color: $colorKey;
}
.title-label {
vertical-align: top;
}
&.lg {
font-size: 1rem;
@@ -51,14 +58,19 @@ $pad: $interiorMargin * $baseRatio;
padding: 0 ($pad / $baseRatio) / 2;
}
&.major,
&.key-edit,
&.key-properties {
&.major {
$bg: $colorBtnMajorBg;
$hc: lighten($bg, 10%);
@include btnSubtle($bg, $hc, $colorBtnMajorFg, $colorBtnMajorFg);
}
&:not(.major) {
// bg, bgHov, fg, ic
@include btnSubtle($colorBtnBg, $colorBtnBgHov, $colorBtnFg, $colorBtnIcon);
}
&.pause-play {
}
&.t-save:before {
content:'\e612';
font-family: symbolsfont;
@@ -97,22 +109,6 @@ $pad: $interiorMargin * $baseRatio;
content: "\000039";
}
}
&.t-export {
&:before {
@extend .ui-symbol;
@extend .icon;
content: '\e623';
}
}
.icon {
font-size: 0.8rem;
}
.title-label {
vertical-align: top;
}
}
.s-icon-btn {
@@ -279,3 +275,4 @@ body.desktop .mini-tab-icon {
color: $colorPausedBg !important;
}
}

View File

@@ -420,63 +420,6 @@ input[type="search"] {
}
}
@mixin sliderKnob() {
$h: 16px;
cursor: pointer;
width: floor($h/1.75);
height: $h;
margin-top: 1 + floor($h/2) * -1;
@include btnSubtle(pullForward($colorBtnBg, 10%));
//border-radius: 50% !important;
}
@mixin sliderKnobRound() {
$h: 12px;
cursor: pointer;
width: $h;
height: $h;
margin-top: 1 + floor($h/2) * -1;
@include btnSubtle(pullForward($colorBtnBg, 10%));
border-radius: 50% !important;
}
input[type="range"] {
// HTML5 range inputs
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
background: transparent; /* Otherwise white in Chrome */
&:focus {
outline: none; /* Removes the blue border. */
}
// Thumb
&::-webkit-slider-thumb {
-webkit-appearance: none;
@include sliderKnobRound();
}
&::-moz-range-thumb {
border: none;
@include sliderKnobRound();
}
&::-ms-thumb {
border: none;
@include sliderKnobRound();
}
// Track
&::-webkit-slider-runnable-track {
width: 100%;
height: 3px;
@include sliderTrack();
}
&::-moz-range-track {
width: 100%;
height: 3px;
@include sliderTrack();
}
}
/******************************************************** DATETIME PICKER */
.l-datetime-picker {
$r1H: 15px;

View File

@@ -167,7 +167,7 @@
}
.pane {
box-sizing: border-box;
&.menu-items {
&.left {
border-right: 1px solid pullForward($colorMenuBg, 10%);
left: 0;
padding-right: $interiorMargin;
@@ -183,53 +183,38 @@
}
}
}
&.menu-item-description {
&.right {
left: auto;
right: 0;
padding: $interiorMargin * 5;
width: $prw;
.desc-area {
&.icon {
color: $colorCreateMenuLgIcon;
font-size: 8em;
margin-bottom: $interiorMargin * 3;
position: relative;
text-align: center;
}
&.title {
color: $colorCreateMenuText;
font-size: 1.2em;
margin-bottom: $interiorMargin * 2;
}
&.description {
color: pushBack($colorCreateMenuText, 20%);
font-size: 0.8em;
line-height: 1.5em;
}
}
}
}
&.mini {
width: 400px;
height: 300px;
.pane {
&.menu-items {
font-size: 0.8em;
}
&.menu-item-description {
padding: $interiorMargin * 3;
.desc-area {
&.icon {
font-size: 4em;
}
&.title {
font-size: 1em;
}
}
}
}
}
.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: $colorCreateMenuText;
font-size: 0.8em;
line-height: 1.5em;
}
}
}
}
.context-menu {
font-size: 0.80rem;
@@ -266,7 +251,3 @@
right: 0;
width: auto;
}
.menus-up .menu {
bottom: $btnStdH; top: auto;
}

View File

@@ -1,376 +0,0 @@
@mixin toiLineHovEffects() {
&:before,
&:after {
background-color: $timeControllerToiLineColorHov;
}
}
.l-time-conductor-holder {
$minW: 500px;
border-top: 1px solid $colorInteriorBorder;
min-width: $minW;
padding-top: $interiorMargin;
}
.time-conductor-icon {
$c: $colorObjHdrIc;
$d: 20px;
background: $c;
border-radius: 4px;
height: $d !important;
width: $d;
position: relative;
// Icon shape: brackets
&:before,
&:after {
content: '';
background: $colorBodyBg;
position: absolute;
}
&:before {
$in: 7px;
left: $in;
top: 0;
right: $in;
bottom: 0;
}
&:after {
$in: 4px;
left: $in;
top: $in;
right: $in;
bottom: $in;
}
// Clock hands
div[class*="hand"] {
$handW: 2px;
$handH: 8px;
@include transform(translate(-50%, -50%));
@include animation-iteration-count(infinite);
@include animation-timing-function(linear);
position: absolute;
height: $handW;
width: $handW;
left: 50%;
top: 50%;
z-index: 2;
&:before {
background-color: $c;
content: '';
display: block;
position: absolute;
width: 100%;
bottom: -1px;
}
&.hand-little {
z-index: 2;
@include animation-duration(12s);
&:before {
//background: red;
height: ceil($handH * 0.7);
}
}
&.hand-big {
z-index: 1;
@include animation-duration(1s);
&:before {
height: $handH;
}
}
}
}
.l-time-conductor {
$knobHOffset: 0px;
$rangeValPad: $interiorMargin;
$rangeValOffset: $sliderKnobW + $interiorMargin;
$r1H: nth($ueTimeControlH, 1);
$r2H: nth($ueTimeControlH, 2);
$r3H: nth($ueTimeControlH, 3);
// Glyphs Todo: replace with refactored CSS approach when that is merged into master
$glyphIconFixed: '\e604';
$glyphIconRealtime: '\43';
$glyphIconLatest: '\44';
position: relative;
> .l-row-elem {
// First order row elements
box-sizing: border-box;
width: 100%;
position: relative;
}
.mode-selector .s-menu-btn,
.time-delta {
&:before {
@extend .ui-symbol;
}
}
.time-delta {
&:before {
color: $colorTimeCondKeyBg;
}
}
.l-time-conductor-inputs-holder,
.l-time-conductor-ticks,
.l-time-conductor-zoom-w {
font-size: 0.8rem;
}
.l-time-conductor-inputs-holder {
$trInputW: 180px;
$hmInputW: 60px;
$ticksBlockerFadeW: 50px;
$iconCalendarW: 16px;
$wBgColor: $colorBodyBg;
height: $r1H;
z-index: 1;
.l-time-range-w {
// Wraps a datetime text input field
height: 100%;
position: absolute;
&.start-w {
@include background-image(linear-gradient(270deg, transparent, $wBgColor $ticksBlockerFadeW));
padding-right: $ticksBlockerFadeW;
}
&.end-w {
@include background-image(linear-gradient(90deg, transparent, $wBgColor $ticksBlockerFadeW));
padding-left: $ticksBlockerFadeW;
right: 0;
text-align: right;
}
input[type="text"] {
@include trans-prop-nice(padding, 250ms);
}
.time-range-input input {
width: $trInputW;
}
.hrs-min-input input {
width: $hmInputW;
}
.icon-calendar {
margin-top: 4px;
}
}
}
.l-time-conductor-ticks {
$c: $colorTick;
height: $r1H;
mct-conductor-axis {
display: block;
position: relative;
width: 100%;
}
.l-axis-holder {
height: $r1H;
position: relative;
width: 100%;
svg {
text-rendering: geometricPrecision;
width: 100%;
height: 100%;
> g {
font-size: 0.9em;
}
path {
// Line beneath ticks
display: none;
}
line {
// Tick marks
stroke: $c;
}
text {
// Tick labels
fill: $c;
}
}
}
}
.l-data-visualization {
background: $colorTimeCondDataVisBg;
height: $r2H;
}
.l-time-conductor-controls {
align-items: center;
margin-top: $interiorMargin;
.l-time-conductor-zoom-w {
@include justify-content(flex-end);
.time-conductor-zoom {
height: $r3H;
min-width: 100px;
width: 20%;
}
.time-conductor-zoom-current-range {
color: $colorTick;
}
}
}
// Fixed
&.fixed-mode {
.time-conductor-icon div[class*="hand"] {
&.hand-little {
@include transform(rotate(120deg));
}
}
}
// Realtime, latest modes
&.realtime-mode,
&.latest-mode {
.time-conductor-icon {
background: $colorTimeCondKeyBg;
div[class*="hand"] {
@include animation-name(clock-hands);
&:before {
background: $colorTimeCondKeyBg;
}
}
}
.l-time-conductor-inputs-holder {
.l-time-range-input-w {
input[type="text"]:not(.error) {
background: transparent;
box-shadow: none;
border-radius: 0;
padding-left: 0;
padding-right: 0;
&:hover,
&:focus {
@include nice-input();
padding: $inputTextP;
}
}
&.start-date {
pointer-events: none;
}
.icon-calendar {
display: none;
}
&.end-date {
display: none;
}
}
}
&.realtime-mode .time-conductor-icon div[class*="hand"] { @include animation-name(clock-hands); }
&.latest-mode .time-conductor-icon div[class*="hand"] {
@include animation-name(clock-hands-sticky);
&.hand-big { @include animation-duration(5s); }
&.hand-little { @include animation-duration(60s); }
}
.l-data-visualization {
background: $colorTimeCondDataVisRtBg !important
}
.mode-selector .s-menu-btn {
@include btnSubtle($colorTimeCondKeyBg, pullForward($colorTimeCondKeyBg, $ltGamma), $colorTimeCondKeyFg);
}
}
&.fixed-mode {
$i: $glyphIconFixed;
.mode-selector .s-menu-btn:before {
content: $i;
}
}
&.realtime-mode {
$i: $glyphIconRealtime;
.time-delta:before {
content: $i;
}
.mode-selector .s-menu-btn:before {
content: $i;
}
}
&.latest-mode {
$i: $glyphIconLatest;
.time-delta:before {
content: $i;
}
.mode-selector .s-menu-btn:before {
content: $i;
}
}
}
.s-time-range-val {
border-radius: $controlCr;
background-color: $colorInputBg;
padding: 1px 1px 0 $interiorMargin;
}
/******************************************************************** MOBILE */
@include phoneandtablet {
.l-time-conductor {
min-width: 0;
.l-time-range-slider-holder,
.l-time-conductor-ticks {
display: none;
}
}
}
@include phone {
.l-time-conductor {
.l-time-conductor-inputs-holder {
&.l-flex-row,
.l-flex-row {
@include align-items(flex-start);
}
.l-time-range-inputs-elem {
&.type-icon {
margin-top: 3px;
}
}
.l-time-conductor-inputs-holder {
@include flex-direction(column);
.l-time-range-input-w:not(:first-child) {
&:not(:first-child) {
margin-top: $interiorMargin;
}
margin-right: 0;
}
.l-time-range-inputs-elem {
&.lbl {
display: none;
}
}
}
}
}
}
@include phonePortrait {
.l-time-conductor {
.l-time-conductor-inputs-holder {
.l-time-conductor-inputs-holder {
@include flex(1 1 auto);
padding-top: 25px; // Make room for the ever lovin' Time Domain Selector
.flex-elem {
@include flex(1 1 auto);
width: 100%;
}
input[type="text"] {
width: 100%;
}
}
}
}
.l-time-domain-selector {
right: auto;
left: 20px;
}
}

View File

@@ -0,0 +1,266 @@
@mixin toiLineHovEffects() {
&:before,
&:after {
background-color: $timeControllerToiLineColorHov;
}
}
.l-time-controller {
$minW: 500px;
$knobHOffset: 0px;
$knobM: ($sliderKnobW + $knobHOffset) * -1;
$rangeValPad: $interiorMargin;
$rangeValOffset: $sliderKnobW + $interiorMargin;
$timeRangeSliderLROffset: 150px + ($sliderKnobW * 2);
$r1H: nth($ueTimeControlH,1); // Not currently used
$r2H: nth($ueTimeControlH,2);
$r3H: nth($ueTimeControlH,3);
min-width: $minW;
font-size: 0.8rem;
.l-time-range-inputs-holder,
.l-time-range-slider-holder,
.l-time-range-ticks-holder
{
box-sizing: border-box;
position: relative;
&:not(:first-child) {
margin-top: $interiorMargin;
}
}
.l-time-range-slider,
.l-time-range-ticks {
@include absPosDefault(0, visible);
left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset;
}
.l-time-range-inputs-holder {
border-top: 1px solid $colorInteriorBorder;
padding-top: $interiorMargin;
&.l-flex-row,
.l-flex-row {
@include align-items(center);
.flex-elem {
height: auto;
line-height: normal;
}
}
.type-icon {
font-size: 120%;
vertical-align: middle;
}
.l-time-range-input-w,
.l-time-range-inputs-elem {
margin-right: $interiorMargin;
.lbl {
color: $colorPlotLabelFg;
}
.ui-symbol.icon {
font-size: 11px;
}
}
.l-time-range-input-w {
// Wraps a datetime text input field
position: relative;
input[type="text"] {
width: 200px;
&.picker-icon {
padding-right: 20px;
}
}
.icon-calendar {
position: absolute;
right: 5px;
top: 5px;
}
}
}
.l-time-range-slider-holder {
height: $r2H;
.range-holder {
box-shadow: none;
background: none;
border: none;
.range {
.toi-line {
$myC: $timeControllerToiLineColor;
$myW: 8px;
@include transform(translateX(50%));
position: absolute;
top: 0; right: 0; bottom: 0px; left: auto;
width: $myW;
height: auto;
z-index: 2;
&:before {
// Vert line
background-color: $myC;
position: absolute;
content: "";
top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1;
width: 1px;
}
}
&:hover .toi-line {
@include toiLineHovEffects;
}
}
}
&:not(:active) {
.knob,
.range {
@include transition-property(left, right);
@include transition-duration(500ms);
@include transition-timing-function(ease-in-out);
}
}
}
.l-time-range-ticks-holder {
height: $r3H;
.l-time-range-ticks {
border-top: 1px solid $colorTick;
.tick {
background-color: $colorTick;
border:none;
height: 5px;
width: 1px;
margin-left: -1px;
position: absolute;
&:first-child {
margin-left: 0;
}
.l-time-range-tick-label {
@include webkitProp(transform, translateX(-50%));
color: $colorPlotLabelFg;
display: inline-block;
font-size: 0.7rem;
position: absolute;
top: 5px;
white-space: nowrap;
z-index: 2;
}
}
}
}
.knob {
z-index: 2;
&:before {
$mTB: 2px;
$grippyW: 3px;
$mLR: ($sliderKnobW - $grippyW)/2;
@include bgStripes($c: pullForward($sliderColorKnob, 20%), $a: 1, $bgsize: 4px, $angle: 0deg);
content: '';
display: block;
position: absolute;
top: $mTB; right: $mLR; bottom: $mTB; left: $mLR;
}
.range-value {
@include trans-prop-nice-fade(.25s);
font-size: 0.7rem;
position: absolute;
height: $r2H;
line-height: $r2H;
white-space: nowrap;
z-index: 1;
}
&:hover {
.range-value {
color: $sliderColorKnobHov;
}
}
&.knob-l {
margin-left: $knobM;
.range-value {
text-align: right;
right: $rangeValOffset;
}
}
&.knob-r {
margin-right: $knobM;
.range-value {
left: $rangeValOffset;
}
&:hover + .range-holder .range .toi-line {
@include toiLineHovEffects;
}
}
}
.l-time-domain-selector {
position: absolute;
right: 0px;
top: $interiorMargin;
}
}
.s-time-range-val {
border-radius: $controlCr;
background-color: $colorInputBg;
padding: 1px 1px 0 $interiorMargin;
}
/******************************************************************** MOBILE */
@include phoneandtablet {
.l-time-controller {
min-width: 0;
.l-time-range-slider-holder,
.l-time-range-ticks-holder {
display: none;
}
}
}
@include phone {
.l-time-controller {
.l-time-range-inputs-holder {
&.l-flex-row,
.l-flex-row {
@include align-items(flex-start);
}
.l-time-range-inputs-elem {
&.type-icon {
margin-top: 3px;
}
}
.t-inputs-w {
@include flex-direction(column);
.l-time-range-input-w:not(:first-child) {
&:not(:first-child) {
margin-top: $interiorMargin;
}
margin-right: 0;
}
.l-time-range-inputs-elem {
&.lbl { display: none; }
}
}
}
}
}
@include phonePortrait {
.l-time-controller {
.l-time-range-inputs-holder {
.t-inputs-w {
@include flex(1 1 auto);
padding-top: 25px; // Make room for the ever lovin' Time Domain Selector
.flex-elem {
@include flex(1 1 auto);
width: 100%;
}
input[type="text"] {
width: 100%;
}
}
}
}
.l-time-domain-selector {
right: auto;
left: 20px;
}
}

View File

@@ -1,10 +1,13 @@
.l-image-main-wrapper,
.l-image-main,
.l-image-main-controlbar,
.l-image-thumbs-wrapper {
@include absPosDefault(0, false);
}
/*************************************** MAIN LAYOUT */
.l-image-main-wrapper {
//@include test();
@if $enableImageryThumbs == true {
bottom: $interiorMargin*2 + $imageThumbsWrapperH;
}
@@ -12,14 +15,16 @@
min-width: 150px;
.l-image-main {
background-color: $colorPlotBg;
margin-bottom: $interiorMargin;
bottom: $imageMainControlBarH + $interiorMargin;
}
.l-image-main-controlbar {
&.l-flex-row { @include align-items(center); }
top: auto;
height: $imageMainControlBarH;
}
}
.l-image-thumbs-wrapper {
//@include test(red);
top: auto;
height: $imageThumbsWrapperH;
}
@@ -39,17 +44,24 @@
background-repeat: no-repeat;
}
.l-image-main {
//cursor: crosshair;
}
.l-image-main-controlbar {
//@include test();
font-size: 0.8em;
line-height: inherit;
line-height: $imageMainControlBarH;
.left, .right {
direction: rtl;
overflow: hidden;
}
.left {
//@include test(red);
text-align: left;
}
.right {
//@include test(green);
z-index: 2;
}
.l-date,
@@ -59,6 +71,7 @@
.l-mag {
direction: ltr;
display: inline-block;
//white-space: nowrap;
&:before {
content: "\000049";
}

View File

@@ -19,6 +19,15 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
@include keyframes(rotation) {
100% { @include transform(rotate(360deg)); }
}
@include keyframes(rotation-centered) {
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@mixin spinner($b: 5px, $c: $colorKey) {
@include transform-origin(center);
@include animation-name(rotation-centered);

View File

@@ -24,10 +24,6 @@
height: 100%;
}
.tabular-holder {
@include absPosDefault();
}
.tabular,
table {
box-sizing: border-box;
@@ -166,41 +162,4 @@ table {
min-width: 150px;
}
}
}
/********************************************************** SPECIFIC TABULAR VIEWS */
.tabular-holder {
&.t-exportable {
$btnExportH: 25px;
.l-view-section {
top: $btnExportH + $interiorMargin;
}
}
}
.child-frame {
.tabular-holder {
&.t-exportable {
$btnExportH: $btnFrameH;
.s-btn.t-export {
@include trans-prop-nice(opacity, $dur: 50ms);
opacity: 0;
}
.l-view-section {
@include trans-prop-nice(top, $dur: 150ms, $delay: 50ms);
top: 0;
}
&:hover {
.s-btn.t-export {
@include trans-prop-nice(opacity, 150ms, 100ms);
opacity: 1;
}
.l-view-section {
@include trans-prop-nice(top, $dur: 150ms);
top: $btnExportH + $interiorMargin;
}
}
}
}
}

View File

@@ -19,10 +19,12 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
.abs.holder-plot {
// Fend off the scrollbar when less than min-height;
right: $interiorMargin;
}
$yBarW: 60px;
$yLabelW: 10px;
$xBarH: 32px;
$legendH: 20px;
$swatchD: 8px;
$plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBarW); // Top, right, bottom, left
.gl-plot {
color: $colorPlotFg;
@@ -30,7 +32,6 @@
position: relative;
width: 100%;
height: 100%;
min-height: $plotMinH;
.gl-plot-local-controls {
@include trans-prop-nice(opacity, 150ms);
@@ -53,17 +54,17 @@
top: auto;
right: 0;
bottom: $interiorMargin;
left: $plotYBarW;
height: $plotXBarH;
left: $yBarW;
height: $xBarH;
width: auto;
overflow: hidden;
}
&.gl-plot-y {
top: $plotLegendH + $interiorMargin;
top: $legendH + $interiorMargin;
right: auto;
bottom: nth($plotDisplayArea, 3);
left: 0;
width: $plotYBarW;
width: $yBarW;
}
}
@@ -145,7 +146,7 @@
@include transform(translateY(-50%));
min-width: 150px; // Need this due to enclosure of .select
top: 50%;
left: $plotYLabelW + $interiorMargin * 2;
left: $yLabelW + $interiorMargin * 2;
}
.t-plot-display-controls {
@@ -173,7 +174,7 @@
right: 0;
bottom: auto;
left: 0;
height: $plotLegendH;
height: $legendH;
overflow-x: hidden;
overflow-y: auto;
}
@@ -235,8 +236,8 @@
.color-swatch {
border-radius: 2px;
display: inline-block;
height: $plotSwatchD;
width: $plotSwatchD;
height: $swatchD;
width: $swatchD;
}
}
}
@@ -248,8 +249,8 @@
padding: 0px $itemPadLR;
.plot-color-swatch {
border: 1px solid $colorBodyBg;
height: $plotSwatchD + 1;
width: $plotSwatchD + 1;
height: $swatchD + 1;
width: $swatchD + 1;
}
}
}

View File

@@ -54,8 +54,7 @@
height: $ohH;
line-height: $ohH;
padding: 0 $interiorMargin;
> span,
&:before {
> span {
font-size: 0.65rem;
}
}

View File

@@ -129,8 +129,6 @@
}
.primary-pane {
// Clip element that have min-widths
overflow: hidden;
// Need to lift up this pane to ensure that 'collapsed' panes don't block user interactions
z-index: 4;
}
@@ -239,10 +237,30 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
top: $ueTopBarH + $interiorMarginLg;
}
.l-object-wrapper {
@extend .abs;
.object-holder-main {
@extend .abs;
}
.l-edit-controls {
//@include trans-prop-nice((opacity, height), 0.25s);
border-bottom: 1px solid $colorInteriorBorder;
line-height: $ueEditToolBarH;
height: 0px;
opacity: 0;
.tool-bar {
right: $interiorMargin;
}
}
}
.l-object-wrapper-inner {
@include trans-prop-nice-resize(0.25s);
}
.object-browse-bar .s-btn,
.top-bar .buttons-main .s-btn,
.top-bar .s-menu-btn,
@@ -270,9 +288,8 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
.left {
padding-right: $interiorMarginLg;
.l-back {
.l-back:not(.s-status-editing) {
margin-right: $interiorMarginLg;
&.s-status-editing { display: none; }
}
}
}
@@ -359,49 +376,19 @@ body.desktop {
.s-status-editing {
.l-object-wrapper {
$t2Dur: $browseToEditAnimMs;
$t1Dur: $t2Dur / 2;
$pulseDur: $editBorderPulseMs;
$bC0: rgba($colorEditAreaFg, 0.5);
$bC100: rgba($colorEditAreaFg, 1);
background-color: $colorEditAreaBg;
@include pulseBorder($colorEditAreaFg, $dur: 1s, $opacity0: 0.3);
border-radius: $controlCr;
border: 1px dotted $bC0;
// Transition 1
@include keyframes(wrapperIn) {
from { border: 0px dotted transparent; padding: 0; }
to { border: 1px dotted $bC0; padding: 5px; }
background-color: $colorEditAreaBg;
border-color: $colorEditAreaFg;
border-width: 2px;
border-style: dotted;
.l-object-wrapper-inner {
@include absPosDefault(3px, hidden);
}
// Do last
@include keyframes(pulseNew) {
from { border-color: $bC0; }
to { border-color: $bC100; }
}
@include animation-name(wrapperIn, pulseNew);
@include animation-duration($t1Dur, $pulseDur);
@include animation-delay(0s, $t1Dur + $t2Dur);
@include animation-direction(normal, alternate);
@include animation-fill-mode(both, none);
@include animation-iteration-count(1, infinite);
@include animation-timing-function(ease-in-out, linear);
.l-edit-controls {
height: 0;
border-bottom: 1px solid $colorInteriorBorder;
// Transition 2: reveal edit controls
@include keyframes(editIn) {
from { border-bottom: 0px solid transparent; height: 0; margin-bottom: 0; }
to { border-bottom: 1px solid $colorInteriorBorder; height: $ueEditToolBarH + $interiorMargin; margin-bottom: $interiorMargin; }
}
@include animToParams(editIn, $dur: $t2Dur, $delay: $t1Dur);
.tool-bar {
right: $interiorMargin;
}
height: $ueEditToolBarH + $interiorMargin;
margin-bottom: $interiorMargin;
opacity: 1;
}
}
}

View File

@@ -23,19 +23,16 @@
<input type="text"
ng-model="textValue"
ng-blur="restoreTextValue(); ngBlur()"
ng-mouseup="ngMouseup()"
ng-class="{
error: textInvalid ||
(structure.validate &&
!structure.validate(ngModel[field])),
'picker-icon': structure.format === 'utc' || !structure.format
}">
</input>
<a class="ui-symbol icon icon-calendar"
ng-if="!picker.active && (structure.format === 'utc' || !structure.format)"
ng-click="picker.active = !picker.active"></a>
<!-- If picker active show icon with no onclick to prevent double registration of clicks -->
<a class="ui-symbol icon icon-calendar" ng-if="picker.active"></a>
</input><a class="ui-symbol icon icon-calendar"
ng-if="structure.format === 'utc' || !structure.format"
ng-click="picker.active = !picker.active">
</a>
<mct-popup ng-if="picker.active">
<div mct-click-elsewhere="picker.active = false">
<mct-control key="'datetime-picker'"

View File

@@ -20,10 +20,10 @@
at runtime from the About dialog for additional information.
-->
<div ng-controller="TimeRangeController as trCtrl" class="l-flex-col">
<form class="l-time-conductor-inputs-holder l-flex-row flex-elem"
<form class="l-time-range-inputs-holder l-flex-row flex-elem"
ng-submit="trCtrl.updateBoundsFromForm()">
<span class="l-time-range-inputs-elem ui-symbol type-icon flex-elem">&#x43;</span>
<span class="l-time-range-inputs-elem l-flex-row flex-elem">
<span class="l-time-range-inputs-elem t-inputs-w l-flex-row flex-elem">
<span class="l-time-range-input-w flex-elem">
<mct-control key="'datetime-field'"
structure="{
@@ -86,7 +86,7 @@
</div>
</div>
<div class="l-time-conductor-ticks flex-elem">
<div class="l-time-range-ticks-holder flex-elem">
<div class="l-time-range-ticks">
<div
ng-repeat="tick in ticks track by $index"

View File

@@ -72,17 +72,6 @@ define(
if ($scope.ngBlur) {
$scope.ngBlur();
}
// If picker is active, dismiss it when valid value has been selected
// This 'if' is to avoid unnecessary validation if picker is not active
if ($scope.picker.active) {
if ($scope.structure.validate && $scope.structure.validate($scope.ngModel[$scope.field])) {
$scope.picker.active = false;
} else if (!$scope.structure.validate) {
//If picker visible, but no validation function, hide picker
$scope.picker.active = false;
}
}
}
}
@@ -104,6 +93,7 @@ define(
$scope.$watch('ngModel[field]', updateFromModel);
$scope.$watch('pickerModel.value', updateFromPicker);
$scope.$watch('textValue', updateFromView);
}
return DateTimeFieldController;

View File

@@ -53,7 +53,7 @@ define([
* format has been otherwise specified
* @param {Function} now a function to return current system time
*/
function TimeRangeController($scope, $timeout, formatService, defaultFormat, now) {
function TimeRangeController($scope, formatService, defaultFormat, now) {
this.$scope = $scope;
this.formatService = formatService;
this.defaultFormat = defaultFormat;
@@ -66,7 +66,6 @@ define([
this.formatter = formatService.getFormat(defaultFormat);
this.formStartChanged = false;
this.formEndChanged = false;
this.$timeout = $timeout;
this.$scope.ticks = [];
@@ -260,23 +259,18 @@ define([
};
TimeRangeController.prototype.updateBoundsFromForm = function () {
var self = this;
//Allow Angular to trigger watches and determine whether values have changed.
this.$timeout(function () {
if (self.formStartChanged) {
self.$scope.ngModel.outer.start =
self.$scope.ngModel.inner.start =
self.$scope.formModel.start;
self.formStartChanged = false;
}
if (self.formEndChanged) {
self.$scope.ngModel.outer.end =
self.$scope.ngModel.inner.end =
self.$scope.formModel.end;
self.formEndChanged = false;
}
});
if (this.formStartChanged) {
this.$scope.ngModel.outer.start =
this.$scope.ngModel.inner.start =
this.$scope.formModel.start;
this.formStartChanged = false;
}
if (this.formEndChanged) {
this.$scope.ngModel.outer.end =
this.$scope.ngModel.inner.end =
this.$scope.formModel.end;
this.formEndChanged = false;
}
};
TimeRangeController.prototype.onFormStartChange = function (

View File

@@ -51,9 +51,7 @@ define(
yMax = yMin + rect.height;
if (x < xMin || x > xMax || y < yMin || y > yMax) {
scope.$apply(function () {
scope.$eval(attrs.mctClickElsewhere);
});
scope.$eval(attrs.mctClickElsewhere);
}
}

View File

@@ -33,7 +33,6 @@ define(
var mockScope,
mockFormatService,
testDefaultFormat,
mockTimeout,
mockNow,
mockFormat,
controller;
@@ -55,10 +54,6 @@ define(
}
beforeEach(function () {
mockTimeout = function (fn) {
return fn();
};
mockScope = jasmine.createSpyObj(
"$scope",
["$apply", "$watch", "$watchCollection"]
@@ -83,7 +78,6 @@ define(
controller = new TimeRangeController(
mockScope,
mockTimeout,
mockFormatService,
testDefaultFormat,
mockNow

View File

@@ -104,8 +104,6 @@ define(
});
it("triggers an evaluation of its related Angular expression", function () {
expect(mockScope.$apply).toHaveBeenCalled();
mockScope.$apply.mostRecentCall.args[0]();
expect(mockScope.$eval)
.toHaveBeenCalledWith(testAttrs.mctClickElsewhere);
});

View File

@@ -14,7 +14,7 @@ $colorAHov: #fff;
$contrastRatioPercent: 7%;
$hoverRatioPercent: 10%;
$basicCr: 3px;
$controlCr: 2px;
$controlCr: 3px;
$smallCr: 2px;
// Buttons and Controls
@@ -52,7 +52,7 @@ $colorGridLines: rgba(#fff, 0.05);
$colorInvokeMenu: #fff;
$colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: pullForward($colorObjHdrTxt, 20%);
$colorTick: pullForward($colorBodyBg, 20%);
$colorTick: rgba(white, 0.2);
// Menu colors
$colorMenuBg: pullForward($colorBodyBg, 23%);
@@ -183,13 +183,12 @@ $scrollbarThumbColorOverlay: lighten($colorOvrBg, 10%);
$scrollbarThumbColorOverlayHov: lighten($scrollbarThumbColorOverlay, 2%);
// Splitter
$splitterD: 17px; // splitterD and $splitterHandleD should both be odd, or even
$splitterD: 25px; // splitterD and HandleD should both be odd, or even
$splitterHandleD: 1px;
$splitterDSm: 17px; // Smaller splitter, used inside elements like a Timeline view
$colorSplitterBg: rgba(#fff, 0.1); //pullForward($colorBodyBg, 5%);
$splitterShdw: rgba(black, 0.4) 0 0 3px;
$splitterEndCr: none;
$colorSplitterHover: pullForward($colorBodyBg, 40%);
$colorSplitterHover: pullForward($colorBodyBg, 15%);
$colorSplitterActive: $colorKey;
// Mobile
@@ -207,10 +206,4 @@ $colorAboutLink: #84b3ff;
// Loading
$colorLoadingFg: $colorAlt1;
$colorLoadingBg: rgba($colorBodyFg, 0.2);
// Time Conductor
$colorTimeCondKeyBg: #4e70dc;
$colorTimeCondKeyFg: #fff;
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 10%);
$colorTimeCondDataVisRtBg: pushBack($colorTimeCondKeyBg, 10%);
$colorLoadingBg: rgba($colorBodyFg, 0.2);

View File

@@ -52,7 +52,7 @@ $colorGridLines: rgba(#000, 0.05);
$colorInvokeMenu: #fff;
$colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: pushBack($colorObjHdrTxt, 30%);
$colorTick: pullForward($colorBodyBg, 30%);
$colorTick: rgba(black, 0.2);
// Menu colors
$colorMenuBg: pushBack($colorBodyBg, 10%);
@@ -183,13 +183,12 @@ $scrollbarThumbColorOverlay: darken($colorOvrBg, 50%);
$scrollbarThumbColorOverlayHov: $scrollbarThumbColorHov;
// Splitter
$splitterD: 16px; // splitterD and $splitterHandleD should both be odd, or even
$splitterD: 24px;
$splitterHandleD: 2px;
$splitterDSm: 16px; // Smaller splitter, used inside elements like a Timeline view
$colorSplitterBg: pullForward($colorBodyBg, 10%);
$splitterShdw: none;
$splitterEndCr: none;
$colorSplitterHover: pullForward($colorBodyBg, 30%);
$colorSplitterHover: none;
$colorSplitterActive: $colorKey;
// Mobile
@@ -208,9 +207,3 @@ $colorAboutLink: #84b3ff;
// Loading
$colorLoadingFg: $colorAlt1;
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Time Conductor
$colorTimeCondKeyBg: #6178dc;
$colorTimeCondKeyFg: #fff;
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 10%);
$colorTimeCondDataVisRtBg: pushBack($colorTimeCondKeyBg, 30%);

View File

@@ -60,6 +60,11 @@ define(
this.$q = $q;
}
function getKey(id) {
var parts = id.split(":");
return parts.length > 1 ? parts.slice(1).join(":") : id;
}
/**
* Checks if the value returned is falsey, and if so returns a
* rejected promise
@@ -126,7 +131,7 @@ define(
// ...and persist
return persistenceFn.apply(persistenceService, [
this.getSpace(),
this.getKey(),
getKey(domainObject.getId()),
domainObject.getModel()
]).then(function (result) {
return rejectIfFalsey(result, self.$q);
@@ -154,7 +159,7 @@ define(
return this.persistenceService.readObject(
this.getSpace(),
this.getKey()
this.domainObject.getId()
).then(updateModel);
};
@@ -173,17 +178,6 @@ define(
return this.identifierService.parse(id).getSpace();
};
/**
* Get the key for this domain object in the given space.
*
* @returns {string} the key of the object in it's space.
*/
PersistenceCapability.prototype.getKey = function () {
var id = this.domainObject.getId();
return this.identifierService.parse(id).getKey();
};
return PersistenceCapability;
}
);

View File

@@ -35,8 +35,7 @@ define(
mockNofificationService,
mockCacheService,
mockQ,
key = "persistence key",
id = "object identifier",
id = "object id",
model,
SPACE = "some space",
persistence,
@@ -102,7 +101,6 @@ define(
});
mockIdentifierService.parse.andReturn(mockIdentifier);
mockIdentifier.getSpace.andReturn(SPACE);
mockIdentifier.getKey.andReturn(key);
persistence = new PersistenceCapability(
mockCacheService,
mockPersistenceService,
@@ -126,7 +124,7 @@ define(
expect(mockPersistenceService.createObject).toHaveBeenCalledWith(
SPACE,
key,
id,
model
);
});
@@ -140,7 +138,7 @@ define(
expect(mockPersistenceService.updateObject).toHaveBeenCalledWith(
SPACE,
key,
id,
model
);
});

View File

@@ -82,6 +82,16 @@ define(
expect(result.a.getModel()).toEqual(model);
});
//TODO: Disabled for NEM Beta
xit("provides a new, fully constituted domain object for a" +
" provided model", function () {
var model = { someKey: "some value"},
result;
result = provider.newObject("a", model);
expect(result.getId()).toEqual("a");
expect(result.getModel()).toEqual(model);
});
});
}
);

View File

@@ -81,7 +81,6 @@ define(
if (phase.toLowerCase() === 'preparing' && !this.dialog) {
this.dialog = this.dialogService.showBlockingMessage({
title: "Preparing to copy objects",
hint: "Do not navigate away from this page or close this browser tab while this message is displayed.",
unknownProgress: true,
severity: "info"
});

View File

@@ -24,7 +24,10 @@ define(
[],
function () {
var DISALLOWED_ACTIONS = ["move"];
var DISALLOWED_ACTIONS = [
"move",
"copy"
];
/**
* This policy prevents performing move/copy/link actions across

View File

@@ -70,25 +70,27 @@ define(
policy = new CrossSpacePolicy();
});
describe("for move actions", function () {
beforeEach(function () {
testActionMetadata.key = 'move';
});
['move', 'copy'].forEach(function (key) {
describe("for " + key + " actions", function () {
beforeEach(function () {
testActionMetadata.key = key;
});
it("allows same-space changes", function () {
expect(policy.allow(mockAction, sameSpaceContext))
.toBe(true);
});
it("allows same-space changes", function () {
expect(policy.allow(mockAction, sameSpaceContext))
.toBe(true);
});
it("disallows cross-space changes", function () {
expect(policy.allow(mockAction, crossSpaceContext))
.toBe(false);
});
it("disallows cross-space changes", function () {
expect(policy.allow(mockAction, crossSpaceContext))
.toBe(false);
});
it("allows actions with no selectedObject", function () {
expect(policy.allow(mockAction, {
domainObject: makeObject('a')
})).toBe(true);
it("allows actions with no selectedObject", function () {
expect(policy.allow(mockAction, {
domainObject: makeObject('a')
})).toBe(true);
});
});
});

View File

@@ -1,85 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"./src/TimeConductor",
"./src/TimeConductorController",
"./src/MCTConductorAxis",
"text!./res/templates/time-conductor.html",
"text!./res/templates/mode-selector/mode-selector.html",
"text!./res/templates/mode-selector/mode-menu.html",
'legacyRegistry'
], function (
TimeConductor,
TimeConductorController,
MCTConductorAxis,
timeConductorTemplate,
modeSelectorTemplate,
modeMenuTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/conductor-v2", {
"extensions": {
"services": [
{
"key": "timeConductor",
"implementation": TimeConductor
}
],
"controllers": [
{
"key": "TimeConductorController",
"implementation": TimeConductorController,
"depends": [
"$scope",
"$timeout",
"timeConductor"
]
}
],
"directives": [
{
"key": "mctConductorAxis",
"implementation": MCTConductorAxis,
"depends": [
"timeConductor"
]
}
],
"representations": [
{
"key": "time-conductor",
"template": timeConductorTemplate
},
{
"key": "mode-selector",
"template": modeSelectorTemplate
},
{
"key": "mode-menu",
"template": modeMenuTemplate
}
]
}
});
});

View File

@@ -1,49 +0,0 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="contents">
<div class="pane left menu-items">
<ul>
<li ng-repeat="(selected, option) in ngModel.options"
ng-click="ngModel.selected=selected">
<a
ng-mouseover="representation.activeMetadata = option"
ng-mouseleave="representation.activeMetadata = undefined">
<span class="ui-symbol icon type-icon">
{{option.glyph}}
</span>
{{option.name}}
</a>
</li>
</ul>
</div>
<div class="pane right menu-item-description">
<div class="desc-area ui-symbol icon type-icon">
{{representation.activeMetadata.glyph}}
</div>
<div class="desc-area title">
{{representation.activeMetadata.name}}
</div>
<div class="desc-area description">
{{representation.activeMetadata.description}}
</div>
</div>
</div>

View File

@@ -1,34 +0,0 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<span ng-controller="ClickAwayController as modeController">
<div class="s-menu-btn"
ng-click="modeController.toggle()">
<span class="title-label">{{ngModel.options[ngModel.selected].label}}</span>
</div>
<div class="menu super-menu mini mode-selector-menu"
ng-show="modeController.isActive()">
<mct-representation mct-object="domainObject"
key="'mode-menu'"
ng-model="ngModel">
</mct-representation>
</div>
</span>

View File

@@ -1,115 +0,0 @@
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
<div ng-controller="TimeConductorController as tcController"
class="holder grows flex-elem l-flex-row l-time-conductor {{modeModel.selected}}-mode">
<div class="flex-elem holder time-conductor-icon">
<div class="hand-little"></div>
<div class="hand-big"></div>
</div>
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
<!-- Holds inputs and ticks -->
<div class="l-time-conductor-ticks l-row-elem l-flex-row flex-elem no-margin">
<form class="abs l-time-conductor-inputs-holder"
ng-submit="tcController.updateBoundsFromForm(formModel)">
<span class="l-time-range-w start-w">
<span class="l-time-range-input-w start-date">
<mct-control key="'datetime-field'"
structure="{
format: 'utc',
validate: tcController.validateStart
}"
ng-model="formModel"
ng-mouseup="tcController.changing['start'] = true"
ng-blur="tcController.changing['start'] = false; tcController.updateBoundsFromForm(formModel)"
field="'start'"
class="time-range-input">
</mct-control>
</span>
<span class="l-time-range-input-w time-delta start-delta"
ng-class="{'hide':(modeModel.selected === 'fixed')}">
-
<mct-control key="'datetime-field'"
structure="{
format: 'duration',
validate: tcController.validateStartDelta
}"
ng-model="formModel"
ng-mouseup="tcController.changing['startDelta'] = true"
ng-blur="tcController.changing['startDelta'] = false; tcController.updateDeltasFromForm(formModel)"
field="'startDelta'"
class="hrs-min-input">
</mct-control>
</span>
</span>
<span class="l-time-range-w end-w">
<span class="l-time-range-input-w time-delta end-delta"
ng-class="{'hide':(modeModel.selected === 'fixed')}">
+
<mct-control key="'datetime-field'"
structure="{
format: 'duration',
validate: tcController.validateEndDelta
}"
ng-model="formModel"
ng-mouseup="tcController.changing['endDelta'] = true"
ng-blur="tcController.changing['endDelta'] = false; tcController.updateDeltasFromForm(formModel)"
field="'endDelta'"
class="hrs-min-input">
</mct-control>
</span>
<span class="l-time-range-input-w end-date"
ng-controller="ToggleController as t2">
<mct-control key="'datetime-field'"
structure="{
format: 'utc',
validate: tcController.validateEnd
}"
ng-model="formModel"
ng-mouseup="tcController.changing['end'] = true"
ng-blur="tcController.changing['end'] = false; tcController.updateBoundsFromForm(formModel)"
field="'end'"
class="time-range-input">
</mct-control>
</span>
</span>
<input type="submit" class="hidden">
</form>
<mct-conductor-axis></mct-conductor-axis>
</div>
<!-- Holds data availability, time of interest -->
<div class="l-data-visualization l-row-elem l-flex-row flex-elem"></div>
<!-- Holds time system and session selectors, and zoom control -->
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
<mct-representation
key="'mode-selector'"
mct-object="domainObject"
ng-model="modeModel"
class="holder flex-elem menus-up mode-selector">
</mct-representation>
<mct-control
key="'menu-button'"
class="holder flex-elem menus-up time-system"
ng-model="conductorModel.timeSystem"
structure="{
text: 'UTC',
options: [
{name: 'UTC', key:'utc', glyph:'\u0043'},
{name: 'SCET', key:'scet', glyph:'\u0043'},
{name: 'SCLK', key:'sclk', glyph:'\u0043'}
]}">
</mct-control>
<!-- Zoom control -->
<div class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">Mars Minutes</span>
<input class="time-conductor-zoom flex-elem" type="range" />
</div>
</div>
</div>
</div>

View File

@@ -1,100 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[
"zepto",
"d3"
],
function ($, d3) {
/**
* The mct-control will dynamically include the control
* for a form element based on a symbolic key. Individual
* controls are defined under the extension category
* `controls`; this allows plug-ins to introduce new form
* control types while still making use of the form
* generator to ensure an overall consistent form style.
* @constructor
* @memberof platform/forms
*/
function MCTConductorAxis(conductor) {
function link(scope, element, attrs, ngModelController) {
var target = element[0].firstChild,
height = target.offsetHeight,
padding = 1;
var vis = d3.select(target)
.append('svg:svg')
.attr('width', '100%')
.attr('height', height);
var xScale = d3.scaleUtc();
var xAxis = d3.axisTop();
// draw x axis with labels and move to the bottom of the chart area
var axisElement = vis.append("g")
.attr("transform", "translate(0," + (height - padding) + ")");
function setScale(start, end) {
var width = target.offsetWidth;
xScale.domain([new Date(start), new Date(end)])
.range([padding, width - padding * 2]);
xAxis.scale(xScale);
axisElement.call(xAxis);
}
scope.resize = function () {
setScale(conductor.bounds().start, conductor.bounds().end);
};
conductor.on('bounds', function (bounds) {
setScale(bounds.start, bounds.end);
});
//Set initial scale.
setScale(conductor.bounds().start, conductor.bounds().end);
}
return {
// Only show at the element level
restrict: "E",
template: "<div class=\"l-axis-holder\" mct-resize=\"resize()\"></div>",
// ngOptions is terminal, so we need to be higher priority
priority: 1000,
// Link function
link: link,
// Pass through Angular's normal input field attributes
scope: {
// Used to choose which form control to use
start: "=",
end: "="
}
};
}
return MCTConductorAxis;
}
);

View File

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

View File

@@ -1,205 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
var FIFTEEN_MINUTES = 15 * 60 * 1000;
function TimeConductorController($scope, $timeout, conductor) {
var self = this;
this.$scope = $scope;
this.$timeout = $timeout;
this.conductor = conductor;
this.startDelta = FIFTEEN_MINUTES;
this.endDelta = 0;
this.changing = {
'start': false,
'end': false
};
$scope.formModel = {
startDelta: this.startDelta,
endDelta: this.endDelta
};
conductor.on('bounds', function (bounds) {
if (!self.changing['start']) {
$scope.formModel.start = bounds.start;
}
if (!self.changing['end']) {
$scope.formModel.end = bounds.end;
}
});
conductor.on('follow', function (follow){
$scope.followMode = follow;
});
Object.keys(TimeConductorController.prototype).filter(function (key) {
return typeof TimeConductorController.prototype[key] === 'function';
}).forEach(function (key) {
self[key] = self[key].bind(self);
});
$scope.$watch('modeModel.selected', this.switchMode);
$scope.modeModel = {
selected: 'fixed',
options: {
'fixed': {
glyph: '\ue604',
label: 'Fixed',
name: 'Fixed Timespan Mode',
description: 'Query and explore data that falls between two fixed datetimes.'
},
'realtime': {
glyph: '\u0043',
label: 'Real-time',
name: 'Real-time Mode',
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
},
'latest': {
glyph: '\u0044',
label: 'LAD',
name: 'LAD Mode',
description: 'Latest Available Data mode monitors real-time streaming data as it comes in. The Time Conductor and displays will only advance when data becomes available.'
}
}
}
$scope.$on('$destroy', function() {
if (self.mode) {
self.mode();
}
});
self.initialize();
}
TimeConductorController.prototype.initialize = function () {
var now = Math.ceil(Date.now() / 1000) * 1000;
//Set the time conductor to some default
this.conductor.bounds({start: now - FIFTEEN_MINUTES, end: now});
this.$scope.modeModel.selected = 'fixed';
this.conductor.follow(false);
};
TimeConductorController.prototype.validateStart = function (start) {
var bounds = this.conductor.bounds();
return this.conductor.validateBounds({start: start, end: bounds.end}) === true;
};
TimeConductorController.prototype.validateEnd = function (end) {
var bounds = this.conductor.bounds();
return this.conductor.validateBounds({start: bounds.start, end: end}) === true;
};
TimeConductorController.prototype.updateBoundsFromForm = function (formModel) {
var newBounds = {start: formModel.start, end: formModel.end};
if (this.conductor.validateBounds(newBounds) === true) {
this.conductor.bounds(newBounds);
}
};
TimeConductorController.prototype.validateStartDelta = function (startDelta) {
return startDelta > 0;
};
TimeConductorController.prototype.validateEndDelta = function (endDelta) {
return endDelta >= 0;
};
TimeConductorController.prototype.validateDeltas = function (formModel) {
// Validate that start Delta is some non-zero value, and that end
// delta is zero or positive (ie. 'now' or some time in the future).
return this.validateStartDelta(formModel.startDelta) && this.validateEndDelta(formModel.endDelta);
};
TimeConductorController.prototype.updateDeltasFromForm = function (formModel) {
if (this.validateDeltas(formModel)) {
//Calculate the previous 'true' end value (without delta)
var oldEnd = this.conductor.bounds().end - this.endDelta || 0;
this.startDelta = formModel.startDelta;
this.endDelta = formModel.endDelta;
var newBounds = {
start: oldEnd - this.startDelta,
end: oldEnd + this.endDelta
};
this.conductor.bounds(newBounds);
}
};
TimeConductorController.prototype.switchMode = function (newMode) {
if (this.mode) {
this.mode();
}
this.mode = TimeConductorController.modes[newMode].call(this);
};
TimeConductorController.modes = {
'fixed': function () {
this.conductor.follow(false);
},
'realtime': function () {
var tickInterval = 1000;
var conductor = this.conductor;
var $timeout = this.$timeout;
var self = this;
conductor.follow(true);
setBoundsToNow(self.startDelta, self.endDelta);
var timeoutPromise = $timeout(tick, tickInterval);
function setBoundsToNow(startDelta, endDelta) {
var now = Math.ceil(Date.now() / 1000) * 1000;
conductor.bounds({start: now - startDelta, end: now + endDelta});
}
function tick() {
setBoundsToNow(self.startDelta, self.endDelta);
timeoutPromise = $timeout(tick, tickInterval)
}
return function destroy() {
$timeout.cancel(timeoutPromise);
}
},
'latest': function () {
//Don't know what to do here yet...
this.conductor.follow(true);
}
};
return TimeConductorController;
}
);

View File

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

View File

@@ -42,7 +42,7 @@ define(
'parent'
];
describe("ConductorRepresenter", function () {
xdescribe("ConductorRepresenter", function () {
var mockThrottle,
mockConductorService,
mockCompile,

View File

@@ -1,18 +1,23 @@
<div class="t-imagery" ng-controller="ImageryController as imagery">
<div class="l-image-main-wrapper l-flex-col"
<div
class="l-image-main-wrapper"
ng-mouseenter="showLocalControls = true;"
ng-mouseleave="showLocalControls = false;">
ng-mouseleave="showLocalControls = false;"
>
<div
class="l-local-controls s-local-controls"
ng-show="false && showLocalControls">
<a class="s-btn"
ng-show="false && showLocalControls"
>
<a
class="s-btn"
ng-click="plot.stepBackPanZoom()"
ng-show="1"
title="Restore previous pan/zoom">
<span class="ui-symbol icon">&lt;</span>
</a>
<a class="s-btn"
<a
class="s-btn"
ng-click="plot.unzoom()"
ng-show="1"
title="Reset pan/zoom">
@@ -20,23 +25,29 @@
</a>
</div>
<div class="l-image-main s-image-main flex-elem grows"
<div
class="l-image-main s-image-main"
ng-class="{ paused: imagery.paused(), stale:false }"
mct-background-image="imagery.getImageUrl()">
mct-background-image="imagery.getImageUrl()"
>
</div>
<div class="l-image-main-controlbar flex-elem l-flex-row">
<div class="l-image-main-controlbar l-flex-row">
<div class="left flex-elem grows">
<a class="s-btn show-thumbs sm hidden"
ng-click="showThumbsBubble = (showThumbsBubble)? false:true"><span class="ui-symbol icon"></span></a>
<a
class="s-btn show-thumbs sm hidden"
ng-click="showThumbsBubble = (showThumbsBubble)? false:true"
><span class="ui-symbol icon"></span></a>
<span class="l-timezone">{{imagery.getZone()}}</span>
<span class="l-time">{{imagery.getTime()}}</span>
<span class="l-date">{{imagery.getDate()}}</span>
</div>
<div class="right flex-elem">
<a class="s-btn pause-play"
<a
class="s-btn pause-play"
ng-click="imagery.paused(!imagery.paused())"
ng-class="{ paused: imagery.paused() }"><span class="ui-symbol icon"></span></a>
ng-class="{ paused: imagery.paused() }"
><span class="ui-symbol icon"></span></a>
<a href="{{imagery.getImageUrl()}}"
ng-if="imagery.getImageUrl()"
target="_blank"
@@ -47,7 +58,8 @@
class="s-btn l-mag s-mag ui-symbol vsm"
ng-click="clipped = false"
ng-show="clipped === true"
title="Not all of image is visible; click to reset."></a>
title="Not all of image is visible; click to reset."
></a>
</div>
</div>
</div>

View File

@@ -109,7 +109,7 @@ define([
{
"key": "HistoricalTableController",
"implementation": HistoricalTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout"]
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
},
{
"key": "RealtimeTableController",

View File

@@ -1,9 +1,8 @@
<div ng-controller="HistoricalTableController" ng-class="{'loading': loading}">
<div ng-controller="HistoricalTableController">
<mct-table
headers="headers"
rows="rows"
enableFilter="true"
enableSort="true"
class="tabular-holder t-exportable">
enableSort="true">
</mct-table>
</div>

View File

@@ -1,9 +1,4 @@
<a class="t-btn l-btn s-btn t-export"
ng-click="exportAsCSV()"
title="Export This View's Data">
Export
</a>
<div class="l-view-section scrolling" style="overflow: auto;" mct-resize="resize()">
<div class="l-view-section scrolling" style="overflow: auto;">
<table class="sizing-table">
<tbody>
<tr>

View File

@@ -4,7 +4,6 @@
rows="rows"
enableFilter="true"
enableSort="true"
class="tabular-holder t-exportable"
auto-scroll="true">
auto-scroll="autoScroll">
</mct-table>
</div>

View File

@@ -25,7 +25,6 @@ define(
'./TelemetryTableController'
],
function (TableController) {
var BATCH_SIZE = 1000;
/**
* Extends TelemetryTableController and adds real-time streaming
@@ -36,82 +35,32 @@ define(
* @param telemetryFormatter
* @constructor
*/
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter, $timeout) {
var self = this;
this.$timeout = $timeout;
this.timeoutHandle = undefined;
this.batchSize = BATCH_SIZE;
$scope.$on("$destroy", function () {
if (self.timeoutHandle) {
self.$timeout.cancel(self.timeoutHandle);
}
});
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter) {
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
}
HistoricalTableController.prototype = Object.create(TableController.prototype);
/**
* Set provided row data on scope, and cancel loading spinner
* @private
* Populates historical data on scope when it becomes available from
* the telemetry API
*/
HistoricalTableController.prototype.doneProcessing = function (rowData) {
this.$scope.rows = rowData;
this.$scope.loading = false;
};
/**
* Processes an array of objects, formatting the telemetry available
* for them and setting it on scope when done
* @private
*/
HistoricalTableController.prototype.processTelemetryObjects = function (objects, offset, start, rowData) {
var telemetryObject = objects[offset],
series,
i = start,
pointCount,
end;
//No more objects to process
if (!telemetryObject) {
return this.doneProcessing(rowData);
}
series = this.handle.getSeries(telemetryObject);
pointCount = series.getPointCount();
end = Math.min(start + this.batchSize, pointCount);
//Process rows in a batch with size not exceeding a maximum length
for (; i < end; i++) {
rowData.push(this.table.getRowValues(telemetryObject,
this.handle.makeDatum(telemetryObject, series, i)));
}
//Done processing all rows for this object.
if (end >= pointCount) {
offset++;
end = 0;
}
// Done processing either a batch or an object, yield process
// before continuing processing
this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, objects, offset, end, rowData));
};
/**
* Populates historical data on scope when it becomes available from
* the telemetry API
*/
HistoricalTableController.prototype.addHistoricalData = function () {
if (this.timeoutHandle) {
this.$timeout.cancel(this.timeoutHandle);
}
var rowData = [],
self = this;
this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, this.handle.getTelemetryObjects(), 0, 0, []));
this.handle.getTelemetryObjects().forEach(function (telemetryObject) {
var series = self.handle.getSeries(telemetryObject) || {},
pointCount = series.getPointCount ? series.getPointCount() : 0,
i = 0;
for (; i < pointCount; i++) {
rowData.push(self.table.getRowValues(telemetryObject,
self.handle.makeDatum(telemetryObject, series, i)));
}
});
this.$scope.rows = rowData;
};
return HistoricalTableController;

View File

@@ -12,7 +12,7 @@ define(
* @param element
* @constructor
*/
function MCTTableController($scope, $timeout, element, exportService) {
function MCTTableController($scope, $timeout, element) {
var self = this;
this.$scope = $scope;
@@ -46,16 +46,6 @@ define(
setDefaults($scope);
$scope.exportAsCSV = function () {
var headers = $scope.displayHeaders;
exportService.exportCSV($scope.displayRows.map(function (row) {
return headers.reduce(function (r, header) {
r[header] = row[header].text;
return r;
}, {});
}), { headers: headers });
};
$scope.toggleSort = function (key) {
if (!$scope.enableSort) {
return;
@@ -86,12 +76,6 @@ define(
*/
$scope.$on('add:row', this.addRow.bind(this));
$scope.$on('remove:row', this.removeRow.bind(this));
/*
* Listen for resize events to trigger recalculation of table width
*/
$scope.resize = this.setElementSizes.bind(this);
}
/**

View File

@@ -38,7 +38,30 @@ define(
function RealtimeTableController($scope, telemetryHandler, telemetryFormatter) {
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
$scope.autoScroll = false;
this.maxRows = 100000;
/*
* Determine if auto-scroll should be enabled. Is enabled
* automatically when telemetry type is string
*/
function hasStringTelemetry(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [];
return ranges.some(function (range) {
return range.format === 'string';
});
}
$scope.$watch('domainObject', function (domainObject) {
//When a domain object becomes available, check whether the
// view should auto-scroll to the bottom.
if (domainObject && hasStringTelemetry(domainObject)) {
$scope.autoScroll = true;
}
});
}
RealtimeTableController.prototype = Object.create(TableController.prototype);
@@ -68,7 +91,6 @@ define(
self.$scope.rows.length - 1);
}
});
this.$scope.loading = false;
};
return RealtimeTableController;

View File

@@ -72,10 +72,10 @@ define(
* Maintain a configuration object on scope that stores column
* configuration. On change, synchronize with object model.
*/
$scope.$watchCollection('configuration.table.columns', function (newColumns, oldColumns) {
if (newColumns !== oldColumns) {
$scope.$watchCollection('configuration.table.columns', function (columns) {
if (columns) {
self.domainObject.useCapability('mutation', function (model) {
model.configuration.table.columns = newColumns;
model.configuration.table.columns = columns;
});
self.domainObject.getCapability('persistence').persist();
}

View File

@@ -83,24 +83,16 @@ define(
* @private
*/
TelemetryTableController.prototype.registerChangeListeners = function () {
var self = this;
this.unregisterChangeListeners();
// When composition changes, re-subscribe to the various
// telemetry subscriptions
this.changeListeners.push(this.$scope.$watchCollection(
'domainObject.getModel().composition',
function (newVal, oldVal) {
if (newVal !== oldVal) {
self.subscribe();
}
})
);
'domainObject.getModel().composition', this.subscribe.bind(this)));
//Change of bounds in time conductor
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds',
this.subscribe.bind(this))
);
this.subscribe.bind(this)));
};
/**
@@ -140,7 +132,6 @@ define(
if (this.handle) {
this.handle.unsubscribe();
}
this.$scope.loading = true;
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
this.$scope.domainObject,

View File

@@ -81,13 +81,7 @@ define(
return {
restrict: "E",
template: TableTemplate,
controller: [
'$scope',
'$timeout',
'$element',
'exportService',
MCTTableController
],
controller: ['$scope', '$timeout', '$element', MCTTableController],
scope: {
headers: "=",
rows: "=",

View File

@@ -30,21 +30,23 @@ define(
var TEST_DOMAIN_VALUE = "some formatted domain value";
describe("A domain column", function () {
var mockDatum,
var mockDataSet,
testMetadata,
mockFormatter,
column;
beforeEach(function () {
mockDataSet = jasmine.createSpyObj(
"data",
["getDomainValue"]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
["formatDomainValue", "formatRangeValue"]
);
testMetadata = {
key: "testKey",
name: "Test Name",
format: "Test Format"
name: "Test Name"
};
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
@@ -55,24 +57,24 @@ define(
expect(column.getTitle()).toEqual("Test Name");
});
describe("when given a datum", function () {
beforeEach(function () {
mockDatum = {
testKey: "testKeyValue"
};
});
xit("looks up data from a data set", function () {
column.getValue(undefined, mockDataSet, 42);
expect(mockDataSet.getDomainValue)
.toHaveBeenCalledWith(42, "testKey");
});
it("looks up data from the given datum", function () {
expect(column.getValue(undefined, mockDatum))
.toEqual({ text: TEST_DOMAIN_VALUE });
});
xit("formats domain values as time", function () {
mockDataSet.getDomainValue.andReturn(402513731000);
it("uses formatter to format domain values as requested", function () {
column.getValue(undefined, mockDatum);
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith("testKeyValue", "Test Format");
});
// Should have just given the value the formatter gave
expect(column.getValue(undefined, mockDataSet, 42).text)
.toEqual(TEST_DOMAIN_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith(402513731000);
expect(mockFormatter.formatRangeValue)
.not.toHaveBeenCalled();
});
});

View File

@@ -34,8 +34,6 @@ define(
mockDomainObject,
mockTable,
mockConfiguration,
mockAngularTimeout,
mockTimeoutHandle,
watches,
controller;
@@ -65,11 +63,6 @@ define(
watches[expression] = callback;
});
mockTimeoutHandle = jasmine.createSpy("timeoutHandle");
mockAngularTimeout = jasmine.createSpy("$timeout");
mockAngularTimeout.andReturn(mockTimeoutHandle);
mockAngularTimeout.cancel = jasmine.createSpy("cancelTimeout");
mockConfiguration = {
'range1': true,
'range2': true,
@@ -114,7 +107,7 @@ define(
]);
mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter, mockAngularTimeout);
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter);
controller.table = mockTable;
controller.handle = mockTelemetryHandle;
});
@@ -170,13 +163,6 @@ define(
controller.addHistoricalData(mockDomainObject, mockSeries);
// Angular timeout is called a minumum of twice, regardless
// of batch size used.
expect(mockAngularTimeout).toHaveBeenCalled();
mockAngularTimeout.mostRecentCall.args[0]();
expect(mockAngularTimeout.calls.length).toEqual(2);
mockAngularTimeout.mostRecentCall.args[0]();
expect(controller.$scope.rows.length).toBe(5);
expect(controller.$scope.rows[0]).toBe(mockRow);
});
@@ -212,7 +198,7 @@ define(
' object composition changes', function () {
controller.registerChangeListeners();
expect(watches['domainObject.getModel().composition']).toBeDefined();
watches['domainObject.getModel().composition']([], []);
watches['domainObject.getModel().composition']();
expect(controller.subscribe).toHaveBeenCalled();
});
@@ -233,78 +219,6 @@ define(
});
});
describe('Yields thread', function () {
var mockSeries,
mockRow;
beforeEach(function () {
mockSeries = {
getPointCount: function () {
return 5;
},
getDomainValue: function () {
return 'Domain Value';
},
getRangeValue: function () {
return 'Range Value';
}
};
mockRow = {'domain': 'Domain Value', 'range': 'Range Value'};
mockTelemetryHandle.makeDatum.andCallFake(function () {
return mockRow;
});
mockTable.getRowValues.andReturn(mockRow);
mockTelemetryHandle.getTelemetryObjects.andReturn([mockDomainObject]);
mockTelemetryHandle.getSeries.andReturn(mockSeries);
});
it('when row count exceeds batch size', function () {
controller.batchSize = 3;
controller.addHistoricalData(mockDomainObject, mockSeries);
//Timeout is called a minimum of two times
expect(mockAngularTimeout).toHaveBeenCalled();
mockAngularTimeout.mostRecentCall.args[0]();
expect(mockAngularTimeout.calls.length).toEqual(2);
mockAngularTimeout.mostRecentCall.args[0]();
//Because it yields, timeout will have been called a
// third time for the batch.
expect(mockAngularTimeout.calls.length).toEqual(3);
mockAngularTimeout.mostRecentCall.args[0]();
expect(controller.$scope.rows.length).toBe(5);
expect(controller.$scope.rows[0]).toBe(mockRow);
});
it('cancelling any outstanding timeouts', function () {
controller.batchSize = 3;
controller.addHistoricalData(mockDomainObject, mockSeries);
expect(mockAngularTimeout).toHaveBeenCalled();
mockAngularTimeout.mostRecentCall.args[0]();
controller.addHistoricalData(mockDomainObject, mockSeries);
expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle);
});
it('cancels timeout on scope destruction', function () {
controller.batchSize = 3;
controller.addHistoricalData(mockDomainObject, mockSeries);
//Destroy is used by parent class as well, so multiple
// calls are made to scope.$on
var destroyCalls = mockScope.$on.calls.filter(function (call) {
return call.args[0] === '$destroy';
});
//Call destroy function
expect(destroyCalls.length).toEqual(2);
destroyCalls[0].args[1]();
expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle);
});
});
});
}
);

View File

@@ -32,8 +32,7 @@ define(
mockScope,
watches,
mockTimeout,
mockElement,
mockExportService;
mockElement;
function promise(value) {
return {
@@ -68,20 +67,11 @@ define(
offsetHeight: 1000
};
mockExportService = jasmine.createSpyObj('exportService', [
'exportCSV'
]);
mockScope.displayHeaders = true;
mockTimeout = jasmine.createSpy('$timeout');
mockTimeout.andReturn(promise(undefined));
controller = new MCTTableController(
mockScope,
mockTimeout,
mockElement,
mockExportService
);
controller = new MCTTableController(mockScope, mockTimeout, mockElement);
spyOn(controller, 'setVisibleRows').andCallThrough();
});
@@ -159,22 +149,6 @@ define(
expect(controller.setVisibleRows).toHaveBeenCalled();
});
it("can be exported as CSV", function () {
controller.setRows(testRows);
controller.setHeaders(Object.keys(testRows[0]));
mockScope.exportAsCSV();
expect(mockExportService.exportCSV)
.toHaveBeenCalled();
mockExportService.exportCSV.mostRecentCall.args[0]
.forEach(function (row, i) {
Object.keys(row).forEach(function (k) {
expect(row[k]).toEqual(
mockScope.displayRows[i][k].text
);
});
});
});
describe('sorting', function () {
var sortedRows;

View File

@@ -155,6 +155,13 @@ define(
expect(mockScope.rows[0].row).toBe(1);
});
});
it('enables autoscroll for event telemetry', function () {
controller.subscribe();
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
expect(mockScope.autoScroll).toBe(true);
});
});
}
);

View File

@@ -91,12 +91,7 @@ define([
"name": "Export Timeline as CSV",
"category": "contextual",
"implementation": ExportTimelineAsCSVAction,
"depends": [
"$log",
"exportService",
"notificationService",
"resources[]"
]
"depends": ["exportService", "notificationService"]
}
],
"constants": [
@@ -472,7 +467,6 @@ define([
"implementation": TimelineZoomController,
"depends": [
"$scope",
"$window",
"TIMELINE_ZOOM_CONFIGURATION"
]
},

View File

@@ -23,13 +23,6 @@
.l-timeline-holder {
@include absPosDefault();
&.split-layout {
>.splitter {
// Top of splitter within Timelines should be 0
top: 0;
}
}
.l-header {
@include user-select(none);
cursor: default;
@@ -65,7 +58,7 @@
}
&.l-tabular-r {
// Start, end, duration, activity modes columns
@include scrollH(scroll);
@include scrollH();
left: $timelineTabularTitleW;
.l-width {
@include absPosDefault(0, visible);
@@ -311,6 +304,11 @@
}
}
.splitter {
// Top of splitter within Timelines should be 0
top: 0;
}
// Ticks
.l-ticks,
.l-subticks {
@@ -333,4 +331,4 @@
&:hover {
background-color: $colorItemTreeHoverBg;
}
}
}

View File

@@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<div class="t-timeline-gantt l-timeline-gantt s-timeline-gantt"
ng-class="timespan ? { sm: gantt.width(timespan, parameters.scroll, parameters.toPixels) < 25 } : {}"
ng-class="{ sm: gantt.width(timespan, parameters.scroll, parameters.toPixels) < 25 }"
title="{{model.name}}"
ng-controller="TimelineGanttController as gantt"
ng-style="timespan ? {

View File

@@ -128,7 +128,7 @@
<div style="overflow: hidden; position: absolute; left: 0; top: 0; right: 0; height: 30px;" mct-scroll-x="scroll.x">
<mct-include key="'timeline-ticks'"
parameters="{
fullWidth: zoomController.width(timelineController.end()),
fullWidth: timelineController.width(zoomController),
start: scroll.x,
width: scroll.width,
step: zoomController.toPixels(zoomController.zoom()),
@@ -141,7 +141,7 @@
mct-scroll-x="scroll.x"
mct-scroll-y="scroll.y">
<div class="l-width-control"
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
ng-style="{ width: timelineController.width(zoomController) + 'px' }">
<div class="t-swimlane s-swimlane l-swimlane"
ng-repeat="swimlane in timelineController.swimlanes()"
ng-class="{
@@ -197,7 +197,7 @@
<div mct-scroll-x="scroll.x"
class="t-pane-r-scroll-h-control l-scroll-control s-scroll-control">
<div class="l-width-control"
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
ng-style="{ width: timelineController.width(zoomController) + 'px' }">
</div>
</div>
</div>

View File

@@ -27,15 +27,11 @@ define([], function () {
* in a domain object's composition.
* @param {number} index the zero-based index of the composition
* element associated with this column
* @param idMap an object containing key value pairs, where keys
* are domain object identifiers and values are whatever
* should appear in CSV output in their place
* @constructor
* @implements {platform/features/timeline.TimelineCSVColumn}
*/
function CompositionColumn(index, idMap) {
function CompositionColumn(index) {
this.index = index;
this.idMap = idMap;
}
CompositionColumn.prototype.name = function () {
@@ -45,9 +41,7 @@ define([], function () {
CompositionColumn.prototype.value = function (domainObject) {
var model = domainObject.getModel(),
composition = model.composition || [];
return composition.length > this.index ?
this.idMap[composition[this.index]] : "";
return (composition[this.index]) || "";
};
return CompositionColumn;

View File

@@ -27,23 +27,14 @@ define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) {
*
* @param exportService the service used to perform the CSV export
* @param notificationService the service used to show notifications
* @param {Array} resources an array of `resources` extensions
* @param context the Action's context
* @implements {Action}
* @constructor
* @memberof {platform/features/timeline}
*/
function ExportTimelineAsCSVAction(
$log,
exportService,
notificationService,
resources,
context
) {
this.$log = $log;
function ExportTimelineAsCSVAction(exportService, notificationService, context) {
this.task = new ExportTimelineAsCSVTask(
exportService,
resources,
context.domainObject
);
this.notificationService = notificationService;
@@ -54,15 +45,13 @@ define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) {
notification = notificationService.notify({
title: "Exporting CSV",
unknownProgress: true
}),
$log = this.$log;
});
return this.task.run()
.then(function () {
notification.dismiss();
})
.catch(function (err) {
$log.warn(err);
.catch(function () {
notification.dismiss();
notificationService.error("Error exporting CSV");
});

View File

@@ -35,13 +35,11 @@ define([
* @constructor
* @memberof {platform/features/timeline}
* @param exportService the service used to export as CSV
* @param resources the `resources` extension category
* @param {DomainObject} domainObject the timeline being exported
*/
function ExportTimelineAsCSVTask(exportService, resources, domainObject) {
function ExportTimelineAsCSVTask(exportService, domainObject) {
this.domainObject = domainObject;
this.exportService = exportService;
this.resources = resources;
}
/**
@@ -52,10 +50,9 @@ define([
*/
ExportTimelineAsCSVTask.prototype.run = function () {
var exportService = this.exportService;
var resources = this.resources;
function doExport(objects) {
var exporter = new TimelineColumnizer(objects, resources),
var exporter = new TimelineColumnizer(objects),
options = { headers: exporter.headers() };
return exporter.rows().then(function (rows) {
return exportService.exportCSV(rows, options);

View File

@@ -23,23 +23,19 @@
define([], function () {
/**
* A column showing identifying domain objects.
* A column showing domain object identifiers.
* @constructor
* @param idMap an object containing key value pairs, where keys
* are domain object identifiers and values are whatever
* should appear in CSV output in their place
* @implements {platform/features/timeline.TimelineCSVColumn}
*/
function IdColumn(idMap) {
this.idMap = idMap;
function IdColumn() {
}
IdColumn.prototype.name = function () {
return "Index";
return "Identifier";
};
IdColumn.prototype.value = function (domainObject) {
return this.idMap[domainObject.getId()];
return domainObject.getId();
};
return IdColumn;

View File

@@ -27,14 +27,10 @@ define([], function () {
* @constructor
* @param {number} index the zero-based index of the composition
* element associated with this column
* @param idMap an object containing key value pairs, where keys
* are domain object identifiers and values are whatever
* should appear in CSV output in their place
* @implements {platform/features/timeline.TimelineCSVColumn}
*/
function ModeColumn(index, idMap) {
function ModeColumn(index) {
this.index = index;
this.idMap = idMap;
}
ModeColumn.prototype.name = function () {
@@ -43,9 +39,8 @@ define([], function () {
ModeColumn.prototype.value = function (domainObject) {
var model = domainObject.getModel(),
modes = (model.relationships || {}).modes || [];
return modes.length > this.index ?
this.idMap[modes[this.index]] : "";
composition = (model.relationships || {}).modes || [];
return (composition[this.index]) || "";
};
return ModeColumn;

View File

@@ -25,15 +25,13 @@ define([
"./ModeColumn",
"./CompositionColumn",
"./MetadataColumn",
"./TimespanColumn",
"./UtilizationColumn"
"./TimespanColumn"
], function (
IdColumn,
ModeColumn,
CompositionColumn,
MetadataColumn,
TimespanColumn,
UtilizationColumn
TimespanColumn
) {
/**
@@ -65,17 +63,15 @@ define([
*
* @param {DomainObject[]} domainObjects the objects to include
* in the exported data
* @param {Array} resources an array of `resources` extensions
* @constructor
* @memberof {platform/features/timeline}
*/
function TimelineColumnizer(domainObjects, resources) {
function TimelineColumnizer(domainObjects) {
var maxComposition = 0,
maxRelationships = 0,
columnNames = {},
columns = [],
foundTimespan = false,
idMap,
i;
function addMetadataProperty(property) {
@@ -86,12 +82,7 @@ define([
}
}
idMap = domainObjects.reduce(function (map, domainObject, index) {
map[domainObject.getId()] = index + 1;
return map;
}, {});
columns.push(new IdColumn(idMap));
columns.push(new IdColumn());
domainObjects.forEach(function (domainObject) {
var model = domainObject.getModel(),
@@ -122,16 +113,12 @@ define([
columns.push(new TimespanColumn(false));
}
resources.forEach(function (resource) {
columns.push(new UtilizationColumn(resource));
});
for (i = 0; i < maxComposition; i += 1) {
columns.push(new CompositionColumn(i, idMap));
columns.push(new CompositionColumn(i));
}
for (i = 0; i < maxRelationships; i += 1) {
columns.push(new ModeColumn(i, idMap));
columns.push(new ModeColumn(i));
}
this.domainObjects = domainObjects;

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