mirror of
https://github.com/nasa/openmct.git
synced 2025-06-30 04:33:03 +00:00
Compare commits
122 Commits
api-type-d
...
v0.10.3
Author | SHA1 | Date | |
---|---|---|---|
e8e9598721 | |||
ce87ad2564 | |||
2a2f6e8142 | |||
3e5057c285 | |||
5485950130 | |||
c8f4568bd0 | |||
cefb40856b | |||
ee7c450e11 | |||
d741e0f23c | |||
8080490e5c | |||
a3443d8077 | |||
53e8e7c688 | |||
dea94e4e68 | |||
d40c7f1821 | |||
c7e7e0c302 | |||
8ac6ac97f0 | |||
1afa4ab329 | |||
c2517c1670 | |||
717ceff02c | |||
6878c59f45 | |||
c00b053aa7 | |||
480e12c3ab | |||
50bd233b0a | |||
79406cf1ed | |||
2d5824c4ab | |||
3480809129 | |||
11bc48c7e0 | |||
d759401b69 | |||
5a2e5ac48f | |||
1144f818cf | |||
0aebecfbb0 | |||
531d40993a | |||
4c097faf88 | |||
5152e64895 | |||
0263237b2c | |||
acd0fae040 | |||
29dd51439d | |||
a1b2175801 | |||
b0f06a2195 | |||
f9c93ca022 | |||
8e0858bb24 | |||
ee0fa0451a | |||
e86e955682 | |||
9913fb48f5 | |||
71c362f016 | |||
fa6e8fd5f9 | |||
23b64951f3 | |||
99590d18f7 | |||
86b31bc040 | |||
d52bfed1df | |||
808ccd0376 | |||
44d6456de1 | |||
a394b95259 | |||
beeefe517a | |||
d02f4041b2 | |||
9fd75ff91e | |||
026ece3956 | |||
214a843dba | |||
1d6880c283 | |||
8ddad9bf4c | |||
f167022eea | |||
76edba1014 | |||
7904989a23 | |||
ea676b4368 | |||
cc2b102256 | |||
b1266abf01 | |||
cc7d0477e8 | |||
5a2d1a746d | |||
4f0e3fdf85 | |||
be9f56107c | |||
787f3815df | |||
35d7d9b380 | |||
8b9c51f303 | |||
661b3d5889 | |||
01c85cb58d | |||
0218f42e2b | |||
d8d0f22889 | |||
d8a097a95a | |||
dc577d4c24 | |||
8f9308de01 | |||
8f7a5e113b | |||
9820f9d9c5 | |||
56ff98cce7 | |||
dade6b2254 | |||
e9cac6eff3 | |||
7fc2fcfa07 | |||
5689279954 | |||
165e158f37 | |||
f301741852 | |||
eda1f23df9 | |||
d15d27af73 | |||
438511c5f7 | |||
3eb960cf5a | |||
699f6ba458 | |||
f21f22d95c | |||
b520d08818 | |||
f9fd97230f | |||
536e2290b8 | |||
73b922facf | |||
ba0d9a186b | |||
80f5cb756d | |||
d7f566088f | |||
a3bcaea7f9 | |||
23c71b7218 | |||
463f7ccf65 | |||
87fe407739 | |||
bb4f1ce7cd | |||
0cc2ba7595 | |||
8162429106 | |||
ed519d89d7 | |||
0e4f6185b8 | |||
1ced47fc2c | |||
677b65d124 | |||
31d31d7819 | |||
d32ef4bc3d | |||
b4faf8991d | |||
0564759481 | |||
24e391edf7 | |||
cf295105d4 | |||
f16a107105 | |||
f683ca44a2 | |||
546cde56a8 |
@ -1,4 +1,4 @@
|
||||
# Open MCT
|
||||
# Open MCT [](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
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 are intended to be added or removed as a single unit. A plug-in for
|
||||
that is 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 with generally involve a `space` parameter in some form, to
|
||||
persistence will 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
8
app.js
@ -75,6 +75,8 @@
|
||||
// Expose everything else as static files
|
||||
app.use(express['static'](options.directory));
|
||||
|
||||
// Finally, open the HTTP server
|
||||
app.listen(options.port);
|
||||
}());
|
||||
// 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)
|
||||
});
|
||||
}());
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "0.10.2-SNAPSHOT",
|
||||
"version": "0.10.3",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {
|
||||
"express": "^4.13.1",
|
||||
|
@ -27,6 +27,7 @@ define([
|
||||
"./src/MenuArrowController",
|
||||
"./src/navigation/NavigationService",
|
||||
"./src/navigation/NavigateAction",
|
||||
"./src/navigation/OrphanNavigationHandler",
|
||||
"./src/windowing/NewTabAction",
|
||||
"./src/windowing/FullscreenAction",
|
||||
"./src/windowing/WindowTitler",
|
||||
@ -47,6 +48,7 @@ define([
|
||||
MenuArrowController,
|
||||
NavigationService,
|
||||
NavigateAction,
|
||||
OrphanNavigationHandler,
|
||||
NewTabAction,
|
||||
FullscreenAction,
|
||||
WindowTitler,
|
||||
@ -91,11 +93,9 @@ define([
|
||||
"$scope",
|
||||
"$route",
|
||||
"$location",
|
||||
"$window",
|
||||
"objectService",
|
||||
"navigationService",
|
||||
"urlService",
|
||||
"policyService",
|
||||
"DEFAULT_PATH"
|
||||
]
|
||||
},
|
||||
@ -199,7 +199,9 @@ define([
|
||||
"implementation": NavigateAction,
|
||||
"depends": [
|
||||
"navigationService",
|
||||
"$q"
|
||||
"$q",
|
||||
"policyService",
|
||||
"$window"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -253,6 +255,14 @@ define([
|
||||
"$rootScope",
|
||||
"$document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"implementation": OrphanNavigationHandler,
|
||||
"depends": [
|
||||
"throttle",
|
||||
"topic",
|
||||
"navigationService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
|
@ -44,11 +44,9 @@ define(
|
||||
$scope,
|
||||
$route,
|
||||
$location,
|
||||
$window,
|
||||
objectService,
|
||||
navigationService,
|
||||
urlService,
|
||||
policyService,
|
||||
defaultPath
|
||||
) {
|
||||
var path = [ROOT_ID].concat(
|
||||
@ -75,25 +73,10 @@ define(
|
||||
|
||||
}
|
||||
|
||||
// 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?");
|
||||
});
|
||||
|
||||
function setScopeObjects(domainObject, navigationAllowed) {
|
||||
if (navigationAllowed) {
|
||||
$scope.navigatedObject = domainObject;
|
||||
$scope.treeModel.selectedObject = domainObject;
|
||||
navigationService.setNavigation(domainObject);
|
||||
updateRoute(domainObject);
|
||||
} else {
|
||||
//If navigation was unsuccessful (ie. blocked), reset
|
||||
@ -103,6 +86,20 @@ 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...
|
||||
|
@ -33,10 +33,12 @@ define(
|
||||
* @constructor
|
||||
* @implements {Action}
|
||||
*/
|
||||
function NavigateAction(navigationService, $q, context) {
|
||||
function NavigateAction(navigationService, $q, policyService, $window, context) {
|
||||
this.domainObject = context.domainObject;
|
||||
this.$q = $q;
|
||||
this.navigationService = navigationService;
|
||||
this.policyService = policyService;
|
||||
this.$window = $window;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,9 +47,20 @@ 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(
|
||||
this.navigationService.setNavigation(this.domainObject)
|
||||
allow() && this.navigationService.setNavigation(this.domainObject)
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,75 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
});
|
@ -37,9 +37,8 @@ define(
|
||||
mockUrlService,
|
||||
mockDomainObject,
|
||||
mockNextObject,
|
||||
mockWindow,
|
||||
mockPolicyService,
|
||||
testDefaultRoot,
|
||||
mockActionCapability,
|
||||
controller;
|
||||
|
||||
function mockPromise(value) {
|
||||
@ -55,25 +54,14 @@ 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(
|
||||
@ -128,6 +116,8 @@ define(
|
||||
mockNextObject.getId.andReturn("next");
|
||||
mockDomainObject.getId.andReturn(testDefaultRoot);
|
||||
|
||||
mockActionCapability = jasmine.createSpyObj('actionCapability', ['perform']);
|
||||
|
||||
instantiateController();
|
||||
});
|
||||
|
||||
@ -211,8 +201,13 @@ 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;
|
||||
return (c === 'context' && mockContext) ||
|
||||
(c === 'action' && mockActionCapability);
|
||||
});
|
||||
mockScope.$on.andReturn(mockUnlisten);
|
||||
// Provide a navigation change
|
||||
@ -225,6 +220,7 @@ define(
|
||||
mockLocation.path.andReturn("/browse/");
|
||||
|
||||
mockNavigationService.setNavigation.andReturn(true);
|
||||
mockActionCapability.perform.andReturn(mockPromise(true));
|
||||
|
||||
// Exercise the Angular workaround
|
||||
mockNavigationService.addListener.mostRecentCall.args[0]();
|
||||
@ -243,6 +239,9 @@ 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);
|
||||
@ -254,11 +253,10 @@ define(
|
||||
it("after failed navigation event resets the selected tree" +
|
||||
" object", function () {
|
||||
mockScope.navigatedObject = mockDomainObject;
|
||||
mockWindow.confirm.andReturn(false);
|
||||
mockPolicyService.allow.andCallFake(function (category, object, context, callback) {
|
||||
callback("unsaved changes");
|
||||
return false;
|
||||
});
|
||||
|
||||
//Return false from navigation action
|
||||
mockActionCapability.perform.andReturn(mockPromise(false));
|
||||
mockNextObject.getCapability.andReturn(mockActionCapability);
|
||||
|
||||
//Simulate a change in selected tree object
|
||||
mockScope.treeModel = {selectedObject: mockDomainObject};
|
||||
|
@ -31,6 +31,8 @@ define(
|
||||
var mockNavigationService,
|
||||
mockQ,
|
||||
mockDomainObject,
|
||||
mockPolicyService,
|
||||
mockWindow,
|
||||
action;
|
||||
|
||||
function mockPromise(value) {
|
||||
@ -44,25 +46,70 @@ define(
|
||||
beforeEach(function () {
|
||||
mockNavigationService = jasmine.createSpyObj(
|
||||
"navigationService",
|
||||
["setNavigation"]
|
||||
[
|
||||
"setNavigation",
|
||||
"getNavigation"
|
||||
]
|
||||
);
|
||||
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 navigate service when performed", function () {
|
||||
it("invokes the policy service to determine if navigation" +
|
||||
" allowed", function () {
|
||||
action.perform();
|
||||
expect(mockNavigationService.setNavigation)
|
||||
.toHaveBeenCalledWith(mockDomainObject);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
it("is only applicable when a domain object is in context", function () {
|
||||
|
@ -0,0 +1,180 @@
|
||||
/*****************************************************************************
|
||||
* 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();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -170,7 +170,7 @@ define([
|
||||
"navigationService",
|
||||
"$log"
|
||||
],
|
||||
"description": "Edit this object.",
|
||||
"description": "Edit",
|
||||
"category": "view-control",
|
||||
"glyph": "p"
|
||||
},
|
||||
|
@ -50,18 +50,24 @@ define(
|
||||
//If the object existed already, navigate to refresh view
|
||||
// with previous object state.
|
||||
if (domainObject.getModel().persisted) {
|
||||
domainObject.getCapability("action").perform("navigate");
|
||||
return domainObject.getCapability("action").perform("navigate");
|
||||
} else {
|
||||
//If the object was new, and user has cancelled, then
|
||||
//navigate back to parent because nothing to show.
|
||||
domainObject.getCapability("location").getOriginal().then(function (original) {
|
||||
return domainObject.getCapability("location").getOriginal().then(function (original) {
|
||||
parent = original.getCapability("context").getParent();
|
||||
parent.getCapability("action").perform("navigate");
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.domainObject.getCapability("editor").cancel()
|
||||
.then(returnToBrowse);
|
||||
|
||||
function cancel(allowed) {
|
||||
return allowed && domainObject.getCapability("editor").cancel();
|
||||
}
|
||||
|
||||
//Do navigation first in order to trigger unsaved changes dialog
|
||||
return returnToBrowse()
|
||||
.then(cancel);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -67,10 +67,17 @@ define(
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
return self.persistenceCapability.refresh().then(function (result) {
|
||||
if (self.domainObject.getModel().persisted !== undefined) {
|
||||
//Fetch clean model from persistence
|
||||
return self.persistenceCapability.refresh().then(function (result) {
|
||||
self.persistPending = false;
|
||||
return result;
|
||||
});
|
||||
} else {
|
||||
self.persistPending = false;
|
||||
return result;
|
||||
});
|
||||
//Model is undefined in persistence, so return undefined.
|
||||
return self.$q.when(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.transactionService.isActive()) {
|
||||
|
@ -24,12 +24,11 @@ define(
|
||||
["../../src/actions/CancelAction"],
|
||||
function (CancelAction) {
|
||||
|
||||
//TODO: Disabled for NEM Beta
|
||||
xdescribe("The Cancel action", function () {
|
||||
var mockLocation,
|
||||
mockDomainObject,
|
||||
mockEditorCapability,
|
||||
mockUrlService,
|
||||
describe("The Cancel action", function () {
|
||||
var mockDomainObject,
|
||||
mockParentObject,
|
||||
capabilities = {},
|
||||
parentCapabilities = {},
|
||||
actionContext,
|
||||
action;
|
||||
|
||||
@ -42,61 +41,114 @@ define(
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockLocation = jasmine.createSpyObj(
|
||||
"$location",
|
||||
["path"]
|
||||
);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getCapability", "hasCapability"]
|
||||
[
|
||||
"getCapability",
|
||||
"hasCapability",
|
||||
"getModel"
|
||||
]
|
||||
);
|
||||
mockEditorCapability = jasmine.createSpyObj(
|
||||
mockDomainObject.getModel.andReturn({});
|
||||
|
||||
mockParentObject = jasmine.createSpyObj(
|
||||
"parentObject",
|
||||
[
|
||||
"getCapability"
|
||||
]
|
||||
);
|
||||
mockParentObject.getCapability.andCallFake(function (name) {
|
||||
return parentCapabilities[name];
|
||||
});
|
||||
|
||||
capabilities.editor = jasmine.createSpyObj(
|
||||
"editor",
|
||||
["save", "cancel"]
|
||||
["save", "cancel", "isEditContextRoot"]
|
||||
);
|
||||
mockUrlService = jasmine.createSpyObj(
|
||||
"urlService",
|
||||
["urlForLocation"]
|
||||
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"
|
||||
]
|
||||
);
|
||||
|
||||
actionContext = {
|
||||
domainObject: mockDomainObject
|
||||
};
|
||||
|
||||
mockDomainObject.hasCapability.andReturn(true);
|
||||
mockDomainObject.getCapability.andReturn(mockEditorCapability);
|
||||
mockEditorCapability.cancel.andReturn(mockPromise(true));
|
||||
mockDomainObject.getCapability.andCallFake(function (name) {
|
||||
return capabilities[name];
|
||||
});
|
||||
|
||||
action = new CancelAction(mockLocation, mockUrlService, actionContext);
|
||||
mockDomainObject.hasCapability.andCallFake(function (name) {
|
||||
return !!capabilities[name];
|
||||
});
|
||||
|
||||
capabilities.editor.cancel.andReturn(mockPromise(true));
|
||||
|
||||
action = new CancelAction(actionContext);
|
||||
|
||||
});
|
||||
|
||||
it("only applies to domain object with an editor capability", function () {
|
||||
it("only applies to domain object that is being edited", function () {
|
||||
capabilities.editor.isEditContextRoot.andReturn(true);
|
||||
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 save functionality when performed", function () {
|
||||
// Verify precondition
|
||||
expect(mockEditorCapability.cancel).not.toHaveBeenCalled();
|
||||
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));
|
||||
action.perform();
|
||||
|
||||
// Should have called cancel
|
||||
expect(mockEditorCapability.cancel).toHaveBeenCalled();
|
||||
expect(capabilities.editor.cancel).toHaveBeenCalled();
|
||||
|
||||
// Definitely shouldn't call save!
|
||||
expect(mockEditorCapability.save).not.toHaveBeenCalled();
|
||||
expect(capabilities.editor.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns to browse when performed", function () {
|
||||
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));
|
||||
action.perform();
|
||||
expect(mockLocation.path).toHaveBeenCalledWith(
|
||||
mockUrlService.urlForLocation("browse", mockDomainObject)
|
||||
);
|
||||
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");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -57,6 +57,15 @@ 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);
|
||||
});
|
||||
|
||||
@ -78,6 +87,20 @@ 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();
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -100,5 +100,6 @@
|
||||
<glyph unicode="" 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="" 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="" 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="" 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="" 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 |
Binary file not shown.
Binary file not shown.
@ -49,7 +49,7 @@ $uePaneMiniTabCollapsedW: 11px;
|
||||
$ueEditLeftPaneW: 75%;
|
||||
$treeSearchInputBarH: 25px;
|
||||
$ueTimeControlH: (33px, 18px, 20px);
|
||||
// Panes
|
||||
/*************** Panes */
|
||||
$ueBrowseLeftPaneTreeMinW: 150px;
|
||||
$ueBrowseLeftPaneTreeMaxW: 35%;
|
||||
$ueBrowseLeftPaneTreeW: 25%;
|
||||
@ -57,48 +57,58 @@ $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;
|
||||
// Bubbles
|
||||
/*************** 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 */
|
||||
$bubbleArwSize: 10px;
|
||||
$bubblePad: $interiorMargin;
|
||||
$bubbleMinW: 100px;
|
||||
$bubbleMaxW: 300px;
|
||||
// Forms
|
||||
/*************** Forms */
|
||||
$reqSymbolW: 15px;
|
||||
$reqSymbolM: $interiorMargin * 2;
|
||||
$reqSymbolFontSize: 0.7em;
|
||||
// Wait Spinner Defaults
|
||||
/*************** Wait Spinner Defaults */
|
||||
$waitSpinnerD: 32px;
|
||||
$waitSpinnerTreeD: 20px;
|
||||
$waitSpinnerBorderW: 5px;
|
||||
@ -124,6 +134,8 @@ $dirImgs: $dirCommonRes + 'images/';
|
||||
|
||||
/************************** TIMINGS */
|
||||
$controlFadeMs: 100ms;
|
||||
$browseToEditAnimMs: 400ms;
|
||||
$editBorderPulseMs: 500ms;
|
||||
|
||||
/************************** LIMITS */
|
||||
$glyphLimit: '\e603';
|
||||
|
@ -39,15 +39,20 @@
|
||||
@include pulse($animName: pulse-subtle, $dur: 500ms, $opacity0: 0.7);
|
||||
}
|
||||
|
||||
@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); }
|
||||
@mixin animTo($animName, $propName, $propValStart, $propValEnd, $dur: 500ms, $delay: 0) {
|
||||
@include keyframes($animName) {
|
||||
from { #{propName}: $propValStart; }
|
||||
to { #{$propName}: $propValEnd; }
|
||||
}
|
||||
@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 animToParams($animName, $dur: 500ms, $delay: 0)
|
||||
}
|
||||
|
||||
@mixin animToParams($animName, $dur: 500ms, $delay: 0) {
|
||||
@include animation-name($animName);
|
||||
@include animation-duration($dur);
|
||||
@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);
|
||||
}
|
@ -348,7 +348,6 @@
|
||||
display: inline-block;
|
||||
font-family: 'symbolsfont';
|
||||
margin-left: $interiorMarginSm;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@mixin nice-textarea($bg: $colorBodyBg, $fg: $colorBodyFg) {
|
||||
|
@ -36,15 +36,7 @@ $pad: $interiorMargin * $baseRatio;
|
||||
padding: 0 $pad;
|
||||
font-size: 0.7rem;
|
||||
vertical-align: top;
|
||||
|
||||
.icon {
|
||||
font-size: 0.8rem;
|
||||
color: $colorKey;
|
||||
}
|
||||
|
||||
.title-label {
|
||||
vertical-align: top;
|
||||
}
|
||||
@include btnSubtle($colorBtnBg, $colorBtnBgHov, $colorBtnFg, $colorBtnIcon);
|
||||
|
||||
&.lg {
|
||||
font-size: 1rem;
|
||||
@ -58,19 +50,14 @@ $pad: $interiorMargin * $baseRatio;
|
||||
padding: 0 ($pad / $baseRatio) / 2;
|
||||
}
|
||||
|
||||
&.major {
|
||||
&.major,
|
||||
&.key-edit,
|
||||
&.key-properties {
|
||||
$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;
|
||||
@ -109,6 +96,22 @@ $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 {
|
||||
@ -275,4 +278,3 @@ body.desktop .mini-tab-icon {
|
||||
color: $colorPausedBg !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
.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;
|
||||
}
|
||||
@ -15,16 +12,14 @@
|
||||
min-width: 150px;
|
||||
.l-image-main {
|
||||
background-color: $colorPlotBg;
|
||||
bottom: $imageMainControlBarH + $interiorMargin;
|
||||
margin-bottom: $interiorMargin;
|
||||
}
|
||||
.l-image-main-controlbar {
|
||||
top: auto;
|
||||
height: $imageMainControlBarH;
|
||||
&.l-flex-row { @include align-items(center); }
|
||||
}
|
||||
}
|
||||
|
||||
.l-image-thumbs-wrapper {
|
||||
//@include test(red);
|
||||
top: auto;
|
||||
height: $imageThumbsWrapperH;
|
||||
}
|
||||
@ -44,24 +39,17 @@
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.l-image-main {
|
||||
//cursor: crosshair;
|
||||
}
|
||||
|
||||
.l-image-main-controlbar {
|
||||
//@include test();
|
||||
font-size: 0.8em;
|
||||
line-height: $imageMainControlBarH;
|
||||
line-height: inherit;
|
||||
.left, .right {
|
||||
direction: rtl;
|
||||
overflow: hidden;
|
||||
}
|
||||
.left {
|
||||
//@include test(red);
|
||||
text-align: left;
|
||||
}
|
||||
.right {
|
||||
//@include test(green);
|
||||
z-index: 2;
|
||||
}
|
||||
.l-date,
|
||||
@ -71,7 +59,6 @@
|
||||
.l-mag {
|
||||
direction: ltr;
|
||||
display: inline-block;
|
||||
//white-space: nowrap;
|
||||
&:before {
|
||||
content: "\000049";
|
||||
}
|
||||
|
@ -24,6 +24,10 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tabular-holder {
|
||||
@include absPosDefault();
|
||||
}
|
||||
|
||||
.tabular,
|
||||
table {
|
||||
box-sizing: border-box;
|
||||
@ -162,4 +166,41 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -19,12 +19,10 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
$yBarW: 60px;
|
||||
$yLabelW: 10px;
|
||||
$xBarH: 32px;
|
||||
$legendH: 20px;
|
||||
$swatchD: 8px;
|
||||
$plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBarW); // Top, right, bottom, left
|
||||
.abs.holder-plot {
|
||||
// Fend off the scrollbar when less than min-height;
|
||||
right: $interiorMargin;
|
||||
}
|
||||
|
||||
.gl-plot {
|
||||
color: $colorPlotFg;
|
||||
@ -32,6 +30,7 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: $plotMinH;
|
||||
|
||||
.gl-plot-local-controls {
|
||||
@include trans-prop-nice(opacity, 150ms);
|
||||
@ -54,17 +53,17 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
|
||||
top: auto;
|
||||
right: 0;
|
||||
bottom: $interiorMargin;
|
||||
left: $yBarW;
|
||||
height: $xBarH;
|
||||
left: $plotYBarW;
|
||||
height: $plotXBarH;
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
&.gl-plot-y {
|
||||
top: $legendH + $interiorMargin;
|
||||
top: $plotLegendH + $interiorMargin;
|
||||
right: auto;
|
||||
bottom: nth($plotDisplayArea, 3);
|
||||
left: 0;
|
||||
width: $yBarW;
|
||||
width: $plotYBarW;
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +145,7 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
|
||||
@include transform(translateY(-50%));
|
||||
min-width: 150px; // Need this due to enclosure of .select
|
||||
top: 50%;
|
||||
left: $yLabelW + $interiorMargin * 2;
|
||||
left: $plotYLabelW + $interiorMargin * 2;
|
||||
}
|
||||
|
||||
.t-plot-display-controls {
|
||||
@ -174,7 +173,7 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
|
||||
right: 0;
|
||||
bottom: auto;
|
||||
left: 0;
|
||||
height: $legendH;
|
||||
height: $plotLegendH;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@ -236,8 +235,8 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
|
||||
.color-swatch {
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
height: $swatchD;
|
||||
width: $swatchD;
|
||||
height: $plotSwatchD;
|
||||
width: $plotSwatchD;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,8 +248,8 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
|
||||
padding: 0px $itemPadLR;
|
||||
.plot-color-swatch {
|
||||
border: 1px solid $colorBodyBg;
|
||||
height: $swatchD + 1;
|
||||
width: $swatchD + 1;
|
||||
height: $plotSwatchD + 1;
|
||||
width: $plotSwatchD + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,8 @@
|
||||
height: $ohH;
|
||||
line-height: $ohH;
|
||||
padding: 0 $interiorMargin;
|
||||
> span {
|
||||
> span,
|
||||
&:before {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
}
|
||||
|
@ -237,30 +237,10 @@ 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,
|
||||
@ -288,8 +268,9 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
|
||||
|
||||
.left {
|
||||
padding-right: $interiorMarginLg;
|
||||
.l-back:not(.s-status-editing) {
|
||||
.l-back {
|
||||
margin-right: $interiorMarginLg;
|
||||
&.s-status-editing { display: none; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -376,19 +357,49 @@ body.desktop {
|
||||
|
||||
.s-status-editing {
|
||||
.l-object-wrapper {
|
||||
@include pulseBorder($colorEditAreaFg, $dur: 1s, $opacity0: 0.3);
|
||||
border-radius: $controlCr;
|
||||
$t2Dur: $browseToEditAnimMs;
|
||||
$t1Dur: $t2Dur / 2;
|
||||
$pulseDur: $editBorderPulseMs;
|
||||
$bC0: rgba($colorEditAreaFg, 0.5);
|
||||
$bC100: rgba($colorEditAreaFg, 1);
|
||||
|
||||
background-color: $colorEditAreaBg;
|
||||
border-color: $colorEditAreaFg;
|
||||
border-width: 2px;
|
||||
border-style: dotted;
|
||||
.l-object-wrapper-inner {
|
||||
@include absPosDefault(3px, hidden);
|
||||
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; }
|
||||
}
|
||||
|
||||
// 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: $ueEditToolBarH + $interiorMargin;
|
||||
margin-bottom: $interiorMargin;
|
||||
opacity: 1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,10 +29,12 @@
|
||||
!structure.validate(ngModel[field])),
|
||||
'picker-icon': structure.format === 'utc' || !structure.format
|
||||
}">
|
||||
</input><a class="ui-symbol icon icon-calendar"
|
||||
ng-if="structure.format === 'utc' || !structure.format"
|
||||
ng-click="picker.active = !picker.active">
|
||||
</a>
|
||||
</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>
|
||||
<mct-popup ng-if="picker.active">
|
||||
<div mct-click-elsewhere="picker.active = false">
|
||||
<mct-control key="'datetime-picker'"
|
||||
|
@ -72,6 +72,17 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +104,6 @@ define(
|
||||
$scope.$watch('ngModel[field]', updateFromModel);
|
||||
$scope.$watch('pickerModel.value', updateFromPicker);
|
||||
$scope.$watch('textValue', updateFromView);
|
||||
|
||||
}
|
||||
|
||||
return DateTimeFieldController;
|
||||
|
@ -51,7 +51,9 @@ define(
|
||||
yMax = yMin + rect.height;
|
||||
|
||||
if (x < xMin || x > xMax || y < yMin || y > yMax) {
|
||||
scope.$eval(attrs.mctClickElsewhere);
|
||||
scope.$apply(function () {
|
||||
scope.$eval(attrs.mctClickElsewhere);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,8 @@ 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);
|
||||
});
|
||||
|
@ -14,7 +14,7 @@ $colorAHov: #fff;
|
||||
$contrastRatioPercent: 7%;
|
||||
$hoverRatioPercent: 10%;
|
||||
$basicCr: 3px;
|
||||
$controlCr: 3px;
|
||||
$controlCr: 2px;
|
||||
$smallCr: 2px;
|
||||
|
||||
// Buttons and Controls
|
||||
@ -183,12 +183,13 @@ $scrollbarThumbColorOverlay: lighten($colorOvrBg, 10%);
|
||||
$scrollbarThumbColorOverlayHov: lighten($scrollbarThumbColorOverlay, 2%);
|
||||
|
||||
// Splitter
|
||||
$splitterD: 25px; // splitterD and HandleD should both be odd, or even
|
||||
$splitterD: 17px; // splitterD and $splitterHandleD 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, 15%);
|
||||
$colorSplitterHover: pullForward($colorBodyBg, 40%);
|
||||
$colorSplitterActive: $colorKey;
|
||||
|
||||
// Mobile
|
||||
|
@ -183,12 +183,12 @@ $scrollbarThumbColorOverlay: darken($colorOvrBg, 50%);
|
||||
$scrollbarThumbColorOverlayHov: $scrollbarThumbColorHov;
|
||||
|
||||
// Splitter
|
||||
$splitterD: 24px;
|
||||
$splitterD: 16px; // splitterD and $splitterHandleD should both be odd, or even
|
||||
$splitterHandleD: 2px;
|
||||
$colorSplitterBg: pullForward($colorBodyBg, 10%);
|
||||
$splitterShdw: none;
|
||||
$splitterEndCr: none;
|
||||
$colorSplitterHover: none;
|
||||
$colorSplitterHover: pullForward($colorBodyBg, 30%);
|
||||
$colorSplitterActive: $colorKey;
|
||||
|
||||
// Mobile
|
||||
|
@ -60,11 +60,6 @@ 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
|
||||
@ -131,7 +126,7 @@ define(
|
||||
// ...and persist
|
||||
return persistenceFn.apply(persistenceService, [
|
||||
this.getSpace(),
|
||||
getKey(domainObject.getId()),
|
||||
this.getKey(),
|
||||
domainObject.getModel()
|
||||
]).then(function (result) {
|
||||
return rejectIfFalsey(result, self.$q);
|
||||
@ -159,7 +154,7 @@ define(
|
||||
|
||||
return this.persistenceService.readObject(
|
||||
this.getSpace(),
|
||||
this.domainObject.getId()
|
||||
this.getKey()
|
||||
).then(updateModel);
|
||||
};
|
||||
|
||||
@ -178,6 +173,17 @@ 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;
|
||||
}
|
||||
);
|
||||
|
@ -35,7 +35,8 @@ define(
|
||||
mockNofificationService,
|
||||
mockCacheService,
|
||||
mockQ,
|
||||
id = "object id",
|
||||
key = "persistence key",
|
||||
id = "object identifier",
|
||||
model,
|
||||
SPACE = "some space",
|
||||
persistence,
|
||||
@ -101,6 +102,7 @@ define(
|
||||
});
|
||||
mockIdentifierService.parse.andReturn(mockIdentifier);
|
||||
mockIdentifier.getSpace.andReturn(SPACE);
|
||||
mockIdentifier.getKey.andReturn(key);
|
||||
persistence = new PersistenceCapability(
|
||||
mockCacheService,
|
||||
mockPersistenceService,
|
||||
@ -124,7 +126,7 @@ define(
|
||||
|
||||
expect(mockPersistenceService.createObject).toHaveBeenCalledWith(
|
||||
SPACE,
|
||||
id,
|
||||
key,
|
||||
model
|
||||
);
|
||||
});
|
||||
@ -138,7 +140,7 @@ define(
|
||||
|
||||
expect(mockPersistenceService.updateObject).toHaveBeenCalledWith(
|
||||
SPACE,
|
||||
id,
|
||||
key,
|
||||
model
|
||||
);
|
||||
});
|
||||
|
@ -82,16 +82,6 @@ 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);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -81,6 +81,7 @@ 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"
|
||||
});
|
||||
|
@ -24,10 +24,7 @@ define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
var DISALLOWED_ACTIONS = [
|
||||
"move",
|
||||
"copy"
|
||||
];
|
||||
var DISALLOWED_ACTIONS = ["move"];
|
||||
|
||||
/**
|
||||
* This policy prevents performing move/copy/link actions across
|
||||
|
@ -70,27 +70,25 @@ define(
|
||||
policy = new CrossSpacePolicy();
|
||||
});
|
||||
|
||||
['move', 'copy'].forEach(function (key) {
|
||||
describe("for " + key + " actions", function () {
|
||||
beforeEach(function () {
|
||||
testActionMetadata.key = key;
|
||||
});
|
||||
describe("for move actions", function () {
|
||||
beforeEach(function () {
|
||||
testActionMetadata.key = 'move';
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -42,7 +42,7 @@ define(
|
||||
'parent'
|
||||
];
|
||||
|
||||
xdescribe("ConductorRepresenter", function () {
|
||||
describe("ConductorRepresenter", function () {
|
||||
var mockThrottle,
|
||||
mockConductorService,
|
||||
mockCompile,
|
||||
|
@ -1,23 +1,18 @@
|
||||
<div class="t-imagery" ng-controller="ImageryController as imagery">
|
||||
<div
|
||||
class="l-image-main-wrapper"
|
||||
<div class="l-image-main-wrapper l-flex-col"
|
||||
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"><</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="s-btn"
|
||||
<a class="s-btn"
|
||||
ng-click="plot.unzoom()"
|
||||
ng-show="1"
|
||||
title="Reset pan/zoom">
|
||||
@ -25,29 +20,23 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="l-image-main s-image-main"
|
||||
<div class="l-image-main s-image-main flex-elem grows"
|
||||
ng-class="{ paused: imagery.paused(), stale:false }"
|
||||
mct-background-image="imagery.getImageUrl()"
|
||||
>
|
||||
mct-background-image="imagery.getImageUrl()">
|
||||
</div>
|
||||
|
||||
<div class="l-image-main-controlbar l-flex-row">
|
||||
<div class="l-image-main-controlbar flex-elem 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"
|
||||
@ -58,8 +47,7 @@
|
||||
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>
|
||||
|
@ -109,7 +109,7 @@ define([
|
||||
{
|
||||
"key": "HistoricalTableController",
|
||||
"implementation": HistoricalTableController,
|
||||
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
||||
"depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout"]
|
||||
},
|
||||
{
|
||||
"key": "RealtimeTableController",
|
||||
|
@ -1,8 +1,9 @@
|
||||
<div ng-controller="HistoricalTableController">
|
||||
<div ng-controller="HistoricalTableController" ng-class="{'loading': loading}">
|
||||
<mct-table
|
||||
headers="headers"
|
||||
rows="rows"
|
||||
enableFilter="true"
|
||||
enableSort="true">
|
||||
enableSort="true"
|
||||
class="tabular-holder t-exportable">
|
||||
</mct-table>
|
||||
</div>
|
@ -1,4 +1,9 @@
|
||||
<div class="l-view-section scrolling" style="overflow: auto;">
|
||||
<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()">
|
||||
<table class="sizing-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -4,6 +4,7 @@
|
||||
rows="rows"
|
||||
enableFilter="true"
|
||||
enableSort="true"
|
||||
auto-scroll="autoScroll">
|
||||
class="tabular-holder t-exportable"
|
||||
auto-scroll="true">
|
||||
</mct-table>
|
||||
</div>
|
@ -25,6 +25,7 @@ define(
|
||||
'./TelemetryTableController'
|
||||
],
|
||||
function (TableController) {
|
||||
var BATCH_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* Extends TelemetryTableController and adds real-time streaming
|
||||
@ -35,32 +36,82 @@ define(
|
||||
* @param telemetryFormatter
|
||||
* @constructor
|
||||
*/
|
||||
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
|
||||
}
|
||||
|
||||
HistoricalTableController.prototype = Object.create(TableController.prototype);
|
||||
|
||||
/**
|
||||
* Populates historical data on scope when it becomes available from
|
||||
* the telemetry API
|
||||
* Set provided row data on scope, and cancel loading spinner
|
||||
* @private
|
||||
*/
|
||||
HistoricalTableController.prototype.addHistoricalData = function () {
|
||||
var rowData = [],
|
||||
self = this;
|
||||
|
||||
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)));
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, this.handle.getTelemetryObjects(), 0, 0, []));
|
||||
};
|
||||
|
||||
return HistoricalTableController;
|
||||
|
@ -12,7 +12,7 @@ define(
|
||||
* @param element
|
||||
* @constructor
|
||||
*/
|
||||
function MCTTableController($scope, $timeout, element) {
|
||||
function MCTTableController($scope, $timeout, element, exportService) {
|
||||
var self = this;
|
||||
|
||||
this.$scope = $scope;
|
||||
@ -46,6 +46,16 @@ 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;
|
||||
@ -76,6 +86,12 @@ 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);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,30 +38,7 @@ 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);
|
||||
@ -91,6 +68,7 @@ define(
|
||||
self.$scope.rows.length - 1);
|
||||
}
|
||||
});
|
||||
this.$scope.loading = false;
|
||||
};
|
||||
|
||||
return RealtimeTableController;
|
||||
|
@ -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 (columns) {
|
||||
if (columns) {
|
||||
$scope.$watchCollection('configuration.table.columns', function (newColumns, oldColumns) {
|
||||
if (newColumns !== oldColumns) {
|
||||
self.domainObject.useCapability('mutation', function (model) {
|
||||
model.configuration.table.columns = columns;
|
||||
model.configuration.table.columns = newColumns;
|
||||
});
|
||||
self.domainObject.getCapability('persistence').persist();
|
||||
}
|
||||
|
@ -83,16 +83,24 @@ 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', this.subscribe.bind(this)));
|
||||
'domainObject.getModel().composition',
|
||||
function (newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
self.subscribe();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
//Change of bounds in time conductor
|
||||
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds',
|
||||
this.subscribe.bind(this)));
|
||||
this.subscribe.bind(this))
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -132,6 +140,7 @@ define(
|
||||
if (this.handle) {
|
||||
this.handle.unsubscribe();
|
||||
}
|
||||
this.$scope.loading = true;
|
||||
|
||||
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
|
||||
this.$scope.domainObject,
|
||||
|
@ -81,7 +81,13 @@ define(
|
||||
return {
|
||||
restrict: "E",
|
||||
template: TableTemplate,
|
||||
controller: ['$scope', '$timeout', '$element', MCTTableController],
|
||||
controller: [
|
||||
'$scope',
|
||||
'$timeout',
|
||||
'$element',
|
||||
'exportService',
|
||||
MCTTableController
|
||||
],
|
||||
scope: {
|
||||
headers: "=",
|
||||
rows: "=",
|
||||
|
@ -30,23 +30,21 @@ define(
|
||||
var TEST_DOMAIN_VALUE = "some formatted domain value";
|
||||
|
||||
describe("A domain column", function () {
|
||||
var mockDataSet,
|
||||
var mockDatum,
|
||||
testMetadata,
|
||||
mockFormatter,
|
||||
column;
|
||||
|
||||
beforeEach(function () {
|
||||
mockDataSet = jasmine.createSpyObj(
|
||||
"data",
|
||||
["getDomainValue"]
|
||||
);
|
||||
|
||||
mockFormatter = jasmine.createSpyObj(
|
||||
"formatter",
|
||||
["formatDomainValue", "formatRangeValue"]
|
||||
);
|
||||
testMetadata = {
|
||||
key: "testKey",
|
||||
name: "Test Name"
|
||||
name: "Test Name",
|
||||
format: "Test Format"
|
||||
};
|
||||
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
|
||||
|
||||
@ -57,24 +55,24 @@ define(
|
||||
expect(column.getTitle()).toEqual("Test Name");
|
||||
});
|
||||
|
||||
xit("looks up data from a data set", function () {
|
||||
column.getValue(undefined, mockDataSet, 42);
|
||||
expect(mockDataSet.getDomainValue)
|
||||
.toHaveBeenCalledWith(42, "testKey");
|
||||
});
|
||||
describe("when given a datum", function () {
|
||||
beforeEach(function () {
|
||||
mockDatum = {
|
||||
testKey: "testKeyValue"
|
||||
};
|
||||
});
|
||||
|
||||
xit("formats domain values as time", function () {
|
||||
mockDataSet.getDomainValue.andReturn(402513731000);
|
||||
it("looks up data from the given datum", function () {
|
||||
expect(column.getValue(undefined, mockDatum))
|
||||
.toEqual({ text: TEST_DOMAIN_VALUE });
|
||||
});
|
||||
|
||||
// Should have just given the value the formatter gave
|
||||
expect(column.getValue(undefined, mockDataSet, 42).text)
|
||||
.toEqual(TEST_DOMAIN_VALUE);
|
||||
it("uses formatter to format domain values as requested", function () {
|
||||
column.getValue(undefined, mockDatum);
|
||||
expect(mockFormatter.formatDomainValue)
|
||||
.toHaveBeenCalledWith("testKeyValue", "Test Format");
|
||||
});
|
||||
|
||||
// Make sure that service interactions were as expected
|
||||
expect(mockFormatter.formatDomainValue)
|
||||
.toHaveBeenCalledWith(402513731000);
|
||||
expect(mockFormatter.formatRangeValue)
|
||||
.not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -34,6 +34,8 @@ define(
|
||||
mockDomainObject,
|
||||
mockTable,
|
||||
mockConfiguration,
|
||||
mockAngularTimeout,
|
||||
mockTimeoutHandle,
|
||||
watches,
|
||||
controller;
|
||||
|
||||
@ -63,6 +65,11 @@ 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,
|
||||
@ -107,7 +114,7 @@ define(
|
||||
]);
|
||||
mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
|
||||
|
||||
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter);
|
||||
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter, mockAngularTimeout);
|
||||
controller.table = mockTable;
|
||||
controller.handle = mockTelemetryHandle;
|
||||
});
|
||||
@ -163,6 +170,13 @@ 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);
|
||||
});
|
||||
@ -198,7 +212,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();
|
||||
});
|
||||
|
||||
@ -219,6 +233,78 @@ 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);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -32,7 +32,8 @@ define(
|
||||
mockScope,
|
||||
watches,
|
||||
mockTimeout,
|
||||
mockElement;
|
||||
mockElement,
|
||||
mockExportService;
|
||||
|
||||
function promise(value) {
|
||||
return {
|
||||
@ -67,11 +68,20 @@ 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);
|
||||
controller = new MCTTableController(
|
||||
mockScope,
|
||||
mockTimeout,
|
||||
mockElement,
|
||||
mockExportService
|
||||
);
|
||||
spyOn(controller, 'setVisibleRows').andCallThrough();
|
||||
});
|
||||
|
||||
@ -149,6 +159,22 @@ 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;
|
||||
|
||||
|
@ -155,13 +155,6 @@ 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);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -91,7 +91,12 @@ define([
|
||||
"name": "Export Timeline as CSV",
|
||||
"category": "contextual",
|
||||
"implementation": ExportTimelineAsCSVAction,
|
||||
"depends": ["exportService", "notificationService"]
|
||||
"depends": [
|
||||
"$log",
|
||||
"exportService",
|
||||
"notificationService",
|
||||
"resources[]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
@ -467,6 +472,7 @@ define([
|
||||
"implementation": TimelineZoomController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$window",
|
||||
"TIMELINE_ZOOM_CONFIGURATION"
|
||||
]
|
||||
},
|
||||
|
@ -23,6 +23,13 @@
|
||||
.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;
|
||||
@ -58,7 +65,7 @@
|
||||
}
|
||||
&.l-tabular-r {
|
||||
// Start, end, duration, activity modes columns
|
||||
@include scrollH();
|
||||
@include scrollH(scroll);
|
||||
left: $timelineTabularTitleW;
|
||||
.l-width {
|
||||
@include absPosDefault(0, visible);
|
||||
@ -304,11 +311,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.splitter {
|
||||
// Top of splitter within Timelines should be 0
|
||||
top: 0;
|
||||
}
|
||||
|
||||
// Ticks
|
||||
.l-ticks,
|
||||
.l-subticks {
|
||||
@ -331,4 +333,4 @@
|
||||
&:hover {
|
||||
background-color: $colorItemTreeHoverBg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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="{ sm: gantt.width(timespan, parameters.scroll, parameters.toPixels) < 25 }"
|
||||
ng-class="timespan ? { sm: gantt.width(timespan, parameters.scroll, parameters.toPixels) < 25 } : {}"
|
||||
title="{{model.name}}"
|
||||
ng-controller="TimelineGanttController as gantt"
|
||||
ng-style="timespan ? {
|
||||
|
@ -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: timelineController.width(zoomController),
|
||||
fullWidth: zoomController.width(timelineController.end()),
|
||||
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: timelineController.width(zoomController) + 'px' }">
|
||||
ng-style="{ width: zoomController.width(timelineController.end()) + '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: timelineController.width(zoomController) + 'px' }">
|
||||
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,11 +27,15 @@ 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) {
|
||||
function CompositionColumn(index, idMap) {
|
||||
this.index = index;
|
||||
this.idMap = idMap;
|
||||
}
|
||||
|
||||
CompositionColumn.prototype.name = function () {
|
||||
@ -41,7 +45,9 @@ define([], function () {
|
||||
CompositionColumn.prototype.value = function (domainObject) {
|
||||
var model = domainObject.getModel(),
|
||||
composition = model.composition || [];
|
||||
return (composition[this.index]) || "";
|
||||
|
||||
return composition.length > this.index ?
|
||||
this.idMap[composition[this.index]] : "";
|
||||
};
|
||||
|
||||
return CompositionColumn;
|
||||
|
@ -27,14 +27,23 @@ 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(exportService, notificationService, context) {
|
||||
function ExportTimelineAsCSVAction(
|
||||
$log,
|
||||
exportService,
|
||||
notificationService,
|
||||
resources,
|
||||
context
|
||||
) {
|
||||
this.$log = $log;
|
||||
this.task = new ExportTimelineAsCSVTask(
|
||||
exportService,
|
||||
resources,
|
||||
context.domainObject
|
||||
);
|
||||
this.notificationService = notificationService;
|
||||
@ -45,13 +54,15 @@ 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 () {
|
||||
.catch(function (err) {
|
||||
$log.warn(err);
|
||||
notification.dismiss();
|
||||
notificationService.error("Error exporting CSV");
|
||||
});
|
||||
|
@ -35,11 +35,13 @@ 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, domainObject) {
|
||||
function ExportTimelineAsCSVTask(exportService, resources, domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.exportService = exportService;
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,9 +52,10 @@ define([
|
||||
*/
|
||||
ExportTimelineAsCSVTask.prototype.run = function () {
|
||||
var exportService = this.exportService;
|
||||
var resources = this.resources;
|
||||
|
||||
function doExport(objects) {
|
||||
var exporter = new TimelineColumnizer(objects),
|
||||
var exporter = new TimelineColumnizer(objects, resources),
|
||||
options = { headers: exporter.headers() };
|
||||
return exporter.rows().then(function (rows) {
|
||||
return exportService.exportCSV(rows, options);
|
||||
|
@ -23,19 +23,23 @@
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* A column showing domain object identifiers.
|
||||
* A column showing identifying domain objects.
|
||||
* @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() {
|
||||
function IdColumn(idMap) {
|
||||
this.idMap = idMap;
|
||||
}
|
||||
|
||||
IdColumn.prototype.name = function () {
|
||||
return "Identifier";
|
||||
return "Index";
|
||||
};
|
||||
|
||||
IdColumn.prototype.value = function (domainObject) {
|
||||
return domainObject.getId();
|
||||
return this.idMap[domainObject.getId()];
|
||||
};
|
||||
|
||||
return IdColumn;
|
||||
|
@ -27,10 +27,14 @@ 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) {
|
||||
function ModeColumn(index, idMap) {
|
||||
this.index = index;
|
||||
this.idMap = idMap;
|
||||
}
|
||||
|
||||
ModeColumn.prototype.name = function () {
|
||||
@ -39,8 +43,9 @@ define([], function () {
|
||||
|
||||
ModeColumn.prototype.value = function (domainObject) {
|
||||
var model = domainObject.getModel(),
|
||||
composition = (model.relationships || {}).modes || [];
|
||||
return (composition[this.index]) || "";
|
||||
modes = (model.relationships || {}).modes || [];
|
||||
return modes.length > this.index ?
|
||||
this.idMap[modes[this.index]] : "";
|
||||
};
|
||||
|
||||
return ModeColumn;
|
||||
|
@ -25,13 +25,15 @@ define([
|
||||
"./ModeColumn",
|
||||
"./CompositionColumn",
|
||||
"./MetadataColumn",
|
||||
"./TimespanColumn"
|
||||
"./TimespanColumn",
|
||||
"./UtilizationColumn"
|
||||
], function (
|
||||
IdColumn,
|
||||
ModeColumn,
|
||||
CompositionColumn,
|
||||
MetadataColumn,
|
||||
TimespanColumn
|
||||
TimespanColumn,
|
||||
UtilizationColumn
|
||||
) {
|
||||
|
||||
/**
|
||||
@ -63,15 +65,17 @@ 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) {
|
||||
function TimelineColumnizer(domainObjects, resources) {
|
||||
var maxComposition = 0,
|
||||
maxRelationships = 0,
|
||||
columnNames = {},
|
||||
columns = [],
|
||||
foundTimespan = false,
|
||||
idMap,
|
||||
i;
|
||||
|
||||
function addMetadataProperty(property) {
|
||||
@ -82,7 +86,12 @@ define([
|
||||
}
|
||||
}
|
||||
|
||||
columns.push(new IdColumn());
|
||||
idMap = domainObjects.reduce(function (map, domainObject, index) {
|
||||
map[domainObject.getId()] = index + 1;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
columns.push(new IdColumn(idMap));
|
||||
|
||||
domainObjects.forEach(function (domainObject) {
|
||||
var model = domainObject.getModel(),
|
||||
@ -113,12 +122,16 @@ 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));
|
||||
columns.push(new CompositionColumn(i, idMap));
|
||||
}
|
||||
|
||||
for (i = 0; i < maxRelationships; i += 1) {
|
||||
columns.push(new ModeColumn(i));
|
||||
columns.push(new ModeColumn(i, idMap));
|
||||
}
|
||||
|
||||
this.domainObjects = domainObjects;
|
||||
|
72
platform/features/timeline/src/actions/UtilizationColumn.js
Normal file
72
platform/features/timeline/src/actions/UtilizationColumn.js
Normal file
@ -0,0 +1,72 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2009-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
/**
|
||||
* A column showing utilization costs associated with activities.
|
||||
* @constructor
|
||||
* @param {string} key the key for the particular cost
|
||||
* @implements {platform/features/timeline.TimelineCSVColumn}
|
||||
*/
|
||||
function UtilizationColumn(resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
UtilizationColumn.prototype.name = function () {
|
||||
var units = {
|
||||
"Kbps": "Kb",
|
||||
"watts": "watt-seconds"
|
||||
}[this.resource.units] || "unknown units";
|
||||
|
||||
return this.resource.name + " (" + units + ")";
|
||||
};
|
||||
|
||||
UtilizationColumn.prototype.value = function (domainObject) {
|
||||
var resource = this.resource;
|
||||
|
||||
function getCost(utilization) {
|
||||
var seconds = (utilization.end - utilization.start) / 1000;
|
||||
return seconds * utilization.value;
|
||||
}
|
||||
|
||||
function getUtilizationValue(utilizations) {
|
||||
utilizations = utilizations.filter(function (utilization) {
|
||||
return utilization.key === resource.key;
|
||||
});
|
||||
|
||||
if (utilizations.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return utilizations.map(getCost).reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return domainObject.hasCapability('utilization') ?
|
||||
domainObject.getCapability('utilization').internal()
|
||||
.then(getUtilizationValue) :
|
||||
"";
|
||||
};
|
||||
|
||||
return UtilizationColumn;
|
||||
});
|
@ -193,6 +193,13 @@ define(
|
||||
* @returns {Promise.<string[]>} a promise for resource identifiers
|
||||
*/
|
||||
resources: promiseResourceKeys,
|
||||
/**
|
||||
* Get the resource utilization associated with this object
|
||||
* directly, not including any resource utilization associated
|
||||
* with contained objects.
|
||||
* @returns {Promise.<Array>}
|
||||
*/
|
||||
internal: promiseInternalUtilization,
|
||||
/**
|
||||
* Get the resource utilization associated with this
|
||||
* object. Results are not sorted. This requires looking
|
||||
|
@ -79,15 +79,6 @@ define(
|
||||
graphPopulator.populate(swimlanePopulator.get());
|
||||
}
|
||||
|
||||
// Get pixel width for right pane, using zoom controller
|
||||
function width(zoomController) {
|
||||
var start = swimlanePopulator.start(),
|
||||
end = swimlanePopulator.end();
|
||||
return zoomController.toPixels(zoomController.duration(
|
||||
Math.max(end - start, MINIMUM_DURATION)
|
||||
));
|
||||
}
|
||||
|
||||
// Refresh resource graphs
|
||||
function refresh() {
|
||||
if (graphPopulator) {
|
||||
@ -121,10 +112,10 @@ define(
|
||||
// Expose active set of swimlanes
|
||||
return {
|
||||
/**
|
||||
* Get the width, in pixels, of the timeline area
|
||||
* @returns {number} width, in pixels
|
||||
* Get the end of the displayed timeline, in milliseconds.
|
||||
* @returns {number} the end of the displayed timeline
|
||||
*/
|
||||
width: width,
|
||||
end: swimlanePopulator.end.bind(swimlanePopulator),
|
||||
/**
|
||||
* Get the swimlanes which should currently be displayed.
|
||||
* @returns {TimelineSwimlane[]} the swimlanes
|
||||
|
@ -22,27 +22,17 @@
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
var PADDING = 0.25;
|
||||
|
||||
/**
|
||||
* Controls the pan-zoom state of a timeline view.
|
||||
* @constructor
|
||||
*/
|
||||
function TimelineZoomController($scope, ZOOM_CONFIGURATION) {
|
||||
function TimelineZoomController($scope, $window, ZOOM_CONFIGURATION) {
|
||||
// Prefer to start with the middle index
|
||||
var zoomLevels = ZOOM_CONFIGURATION.levels || [1000],
|
||||
zoomIndex = Math.floor(zoomLevels.length / 2),
|
||||
tickWidth = ZOOM_CONFIGURATION.width || 200,
|
||||
bounds = { x: 0, width: tickWidth },
|
||||
duration = 86400000; // Default duration in view
|
||||
|
||||
// Round a duration to a larger value, to ensure space for editing
|
||||
function roundDuration(value) {
|
||||
// Ensure there's always an extra day or so
|
||||
var tickCount = bounds.width / tickWidth,
|
||||
sz = zoomLevels[zoomLevels.length - 1] * tickCount;
|
||||
value *= 1.25; // Add 25% padding to start
|
||||
return Math.ceil(value / sz) * sz;
|
||||
}
|
||||
tickWidth = ZOOM_CONFIGURATION.width || 200;
|
||||
|
||||
function toMillis(pixels) {
|
||||
return (pixels / tickWidth) * zoomLevels[zoomIndex];
|
||||
@ -63,14 +53,21 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
function setScroll(x) {
|
||||
$window.requestAnimationFrame(function () {
|
||||
$scope.scroll.x = x;
|
||||
$scope.$apply();
|
||||
});
|
||||
}
|
||||
|
||||
function initializeZoomFromTimespan(timespan) {
|
||||
var timelineDuration = timespan.getDuration();
|
||||
zoomIndex = 0;
|
||||
while (toMillis(bounds.width) < timelineDuration &&
|
||||
while (toMillis($scope.scroll.width) < timelineDuration &&
|
||||
zoomIndex < zoomLevels.length - 1) {
|
||||
zoomIndex += 1;
|
||||
}
|
||||
bounds.x = toPixels(timespan.getStart());
|
||||
setScroll(toPixels(timespan.getStart()));
|
||||
}
|
||||
|
||||
function initializeZoom() {
|
||||
@ -80,9 +77,6 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch("scroll", function (scroll) {
|
||||
bounds = scroll;
|
||||
});
|
||||
$scope.$watch("domainObject", initializeZoom);
|
||||
|
||||
return {
|
||||
@ -100,9 +94,10 @@ define(
|
||||
zoom: function (amount) {
|
||||
// Update the zoom level if called with an argument
|
||||
if (arguments.length > 0 && !isNaN(amount)) {
|
||||
var bounds = $scope.scroll;
|
||||
var center = this.toMillis(bounds.x + bounds.width / 2);
|
||||
setZoomLevel(zoomIndex + amount);
|
||||
bounds.x = this.toPixels(center) - bounds.width / 2;
|
||||
setScroll(this.toPixels(center) - bounds.width / 2);
|
||||
}
|
||||
return zoomLevels[zoomIndex];
|
||||
},
|
||||
@ -124,16 +119,14 @@ define(
|
||||
*/
|
||||
toMillis: toMillis,
|
||||
/**
|
||||
* Get or set the current displayed duration. If used as a
|
||||
* setter, this will typically be rounded up to ensure extra
|
||||
* space is available at the right.
|
||||
* @returns {number} duration, in milliseconds
|
||||
* Get the pixel width necessary to fit the specified
|
||||
* timestamp, expressed as an offset in milliseconds from
|
||||
* the start of the timeline.
|
||||
* @param {number} timestamp the time to display
|
||||
*/
|
||||
duration: function (value) {
|
||||
if (arguments.length > 0) {
|
||||
duration = roundDuration(value);
|
||||
}
|
||||
return duration;
|
||||
width: function (timestamp) {
|
||||
var pixels = Math.ceil(toPixels(timestamp * (1 + PADDING)));
|
||||
return Math.max($scope.scroll.width, pixels);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -23,13 +23,20 @@
|
||||
define(
|
||||
['../../src/actions/CompositionColumn'],
|
||||
function (CompositionColumn) {
|
||||
var TEST_IDS = ['a', 'b', 'c', 'd', 'e', 'f'];
|
||||
|
||||
describe("CompositionColumn", function () {
|
||||
var testIndex,
|
||||
testIdMap,
|
||||
column;
|
||||
|
||||
beforeEach(function () {
|
||||
testIndex = 3;
|
||||
column = new CompositionColumn(testIndex);
|
||||
testIdMap = TEST_IDS.reduce(function (map, id, index) {
|
||||
map[id] = index;
|
||||
return map;
|
||||
}, {});
|
||||
column = new CompositionColumn(testIndex, testIdMap);
|
||||
});
|
||||
|
||||
it("includes a one-based index in its name", function () {
|
||||
@ -46,15 +53,13 @@ define(
|
||||
'domainObject',
|
||||
['getId', 'getModel', 'getCapability']
|
||||
);
|
||||
testModel = {
|
||||
composition: ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
};
|
||||
testModel = { composition: TEST_IDS };
|
||||
mockDomainObject.getModel.andReturn(testModel);
|
||||
});
|
||||
|
||||
it("returns a corresponding identifier", function () {
|
||||
it("returns a corresponding value from the map", function () {
|
||||
expect(column.value(mockDomainObject))
|
||||
.toEqual(testModel.composition[testIndex]);
|
||||
.toEqual(testIdMap[testModel.composition[testIndex]]);
|
||||
});
|
||||
|
||||
it("returns nothing when composition is exceeded", function () {
|
||||
|
@ -24,7 +24,8 @@ define(
|
||||
['../../src/actions/ExportTimelineAsCSVAction'],
|
||||
function (ExportTimelineAsCSVAction) {
|
||||
describe("ExportTimelineAsCSVAction", function () {
|
||||
var mockExportService,
|
||||
var mockLog,
|
||||
mockExportService,
|
||||
mockNotificationService,
|
||||
mockNotification,
|
||||
mockDomainObject,
|
||||
@ -39,6 +40,13 @@ define(
|
||||
['getId', 'getModel', 'getCapability', 'hasCapability']
|
||||
);
|
||||
mockType = jasmine.createSpyObj('type', ['instanceOf']);
|
||||
|
||||
mockLog = jasmine.createSpyObj('$log', [
|
||||
'warn',
|
||||
'error',
|
||||
'info',
|
||||
'debug'
|
||||
]);
|
||||
mockExportService = jasmine.createSpyObj(
|
||||
'exportService',
|
||||
['exportCSV']
|
||||
@ -63,8 +71,10 @@ define(
|
||||
testContext = { domainObject: mockDomainObject };
|
||||
|
||||
action = new ExportTimelineAsCSVAction(
|
||||
mockLog,
|
||||
mockExportService,
|
||||
mockNotificationService,
|
||||
[],
|
||||
testContext
|
||||
);
|
||||
});
|
||||
@ -129,8 +139,11 @@ define(
|
||||
});
|
||||
|
||||
describe("and an error occurs", function () {
|
||||
var testError;
|
||||
|
||||
beforeEach(function () {
|
||||
testPromise.reject();
|
||||
testError = { someProperty: "some value" };
|
||||
testPromise.reject(testError);
|
||||
waitsFor(function () {
|
||||
return mockCallback.calls.length > 0;
|
||||
});
|
||||
@ -145,6 +158,10 @@ define(
|
||||
expect(mockNotificationService.error)
|
||||
.toHaveBeenCalledWith(jasmine.any(String));
|
||||
});
|
||||
|
||||
it("logs the root cause", function () {
|
||||
expect(mockLog.warn).toHaveBeenCalledWith(testError);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -52,6 +52,7 @@ define(
|
||||
|
||||
task = new ExportTimelineAsCSVTask(
|
||||
mockExportService,
|
||||
[],
|
||||
mockDomainObject
|
||||
);
|
||||
});
|
||||
|
@ -24,10 +24,12 @@ define(
|
||||
['../../src/actions/IdColumn'],
|
||||
function (IdColumn) {
|
||||
describe("IdColumn", function () {
|
||||
var column;
|
||||
var testIdMap,
|
||||
column;
|
||||
|
||||
beforeEach(function () {
|
||||
column = new IdColumn();
|
||||
testIdMap = { "foo": "bar" };
|
||||
column = new IdColumn(testIdMap);
|
||||
});
|
||||
|
||||
it("has a name", function () {
|
||||
@ -47,9 +49,9 @@ define(
|
||||
mockDomainObject.getId.andReturn(testId);
|
||||
});
|
||||
|
||||
it("provides a domain object's identifier", function () {
|
||||
it("provides a value mapped from domain object's identifier", function () {
|
||||
expect(column.value(mockDomainObject))
|
||||
.toEqual(testId);
|
||||
.toEqual(testIdMap[testId]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -23,13 +23,20 @@
|
||||
define(
|
||||
['../../src/actions/ModeColumn'],
|
||||
function (ModeColumn) {
|
||||
var TEST_IDS = ['a', 'b', 'c', 'd', 'e', 'f'];
|
||||
|
||||
describe("ModeColumn", function () {
|
||||
var testIndex,
|
||||
testIdMap,
|
||||
column;
|
||||
|
||||
beforeEach(function () {
|
||||
testIndex = 3;
|
||||
column = new ModeColumn(testIndex);
|
||||
testIdMap = TEST_IDS.reduce(function (map, id, index) {
|
||||
map[id] = index;
|
||||
return map;
|
||||
}, {});
|
||||
column = new ModeColumn(testIndex, testIdMap);
|
||||
});
|
||||
|
||||
it("includes a one-based index in its name", function () {
|
||||
@ -48,15 +55,15 @@ define(
|
||||
);
|
||||
testModel = {
|
||||
relationships: {
|
||||
modes: ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
modes: TEST_IDS
|
||||
}
|
||||
};
|
||||
mockDomainObject.getModel.andReturn(testModel);
|
||||
});
|
||||
|
||||
it("returns a corresponding identifier", function () {
|
||||
it("returns a corresponding value from the map", function () {
|
||||
expect(column.value(mockDomainObject))
|
||||
.toEqual(testModel.relationships.modes[testIndex]);
|
||||
.toEqual(testIdMap[testModel.relationships.modes[testIndex]]);
|
||||
});
|
||||
|
||||
it("returns nothing when relationships are exceeded", function () {
|
||||
|
@ -75,7 +75,7 @@ define(
|
||||
return c === 'metadata' && testMetadata;
|
||||
});
|
||||
|
||||
exporter = new TimelineColumnizer(mockDomainObjects);
|
||||
exporter = new TimelineColumnizer(mockDomainObjects, []);
|
||||
});
|
||||
|
||||
describe("rows", function () {
|
||||
@ -94,13 +94,6 @@ define(
|
||||
it("include one row per domain object", function () {
|
||||
expect(rows.length).toEqual(mockDomainObjects.length);
|
||||
});
|
||||
|
||||
it("includes identifiers for each domain object", function () {
|
||||
rows.forEach(function (row, index) {
|
||||
var id = mockDomainObjects[index].getId();
|
||||
expect(row.indexOf(id)).not.toEqual(-1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("headers", function () {
|
||||
|
@ -214,23 +214,6 @@ define(
|
||||
|
||||
});
|
||||
|
||||
it("reports full scrollable width using zoom controller", function () {
|
||||
var mockZoom = jasmine.createSpyObj('zoom', ['toPixels', 'duration']);
|
||||
mockZoom.toPixels.andReturn(54321);
|
||||
mockZoom.duration.andReturn(12345);
|
||||
|
||||
// Initially populate
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
|
||||
expect(controller.width(mockZoom)).toEqual(54321);
|
||||
// Verify interactions; we took zoom's duration for our start/end,
|
||||
// and converted it to pixels.
|
||||
// First, check that we used the start/end (from above)
|
||||
expect(mockZoom.duration).toHaveBeenCalledWith(12321 - 42);
|
||||
// Next, verify that the result was passed to toPixels
|
||||
expect(mockZoom.toPixels).toHaveBeenCalledWith(12345);
|
||||
});
|
||||
|
||||
it("provides drag handles", function () {
|
||||
// TimelineDragPopulator et al are tested for these,
|
||||
// so just verify that handles are indeed exposed.
|
||||
|
@ -28,6 +28,7 @@ define(
|
||||
describe("The timeline zoom state controller", function () {
|
||||
var testConfiguration,
|
||||
mockScope,
|
||||
mockWindow,
|
||||
controller;
|
||||
|
||||
beforeEach(function () {
|
||||
@ -35,10 +36,16 @@ define(
|
||||
levels: [1000, 2000, 3500],
|
||||
width: 12321
|
||||
};
|
||||
mockScope = jasmine.createSpyObj("$scope", ['$watch']);
|
||||
mockScope =
|
||||
jasmine.createSpyObj("$scope", ['$watch', '$apply']);
|
||||
mockScope.commit = jasmine.createSpy('commit');
|
||||
mockScope.scroll = { x: 0, width: 1000 };
|
||||
mockWindow = {
|
||||
requestAnimationFrame: jasmine.createSpy('raf')
|
||||
};
|
||||
controller = new TimelineZoomController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
testConfiguration
|
||||
);
|
||||
});
|
||||
@ -47,12 +54,6 @@ define(
|
||||
expect(controller.zoom()).toEqual(2000);
|
||||
});
|
||||
|
||||
it("allows duration to be changed", function () {
|
||||
var initial = controller.duration();
|
||||
controller.duration(initial * 3.33);
|
||||
expect(controller.duration() > initial).toBeTruthy();
|
||||
});
|
||||
|
||||
it("handles time-to-pixel conversions", function () {
|
||||
var zoomLevel = controller.zoom();
|
||||
expect(controller.toPixels(zoomLevel)).toEqual(12321);
|
||||
@ -70,11 +71,6 @@ define(
|
||||
expect(controller.zoom()).toEqual(3500);
|
||||
});
|
||||
|
||||
it("observes scroll bounds", function () {
|
||||
expect(mockScope.$watch)
|
||||
.toHaveBeenCalledWith("scroll", jasmine.any(Function));
|
||||
});
|
||||
|
||||
describe("when watches have fired", function () {
|
||||
var mockDomainObject,
|
||||
mockPromise,
|
||||
@ -115,6 +111,10 @@ define(
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
call.args[1](mockScope[call.args[0]]);
|
||||
});
|
||||
|
||||
mockWindow.requestAnimationFrame.calls.forEach(function (call) {
|
||||
call.args[0]();
|
||||
});
|
||||
});
|
||||
|
||||
it("zooms to fit the timeline", function () {
|
||||
@ -125,6 +125,27 @@ define(
|
||||
expect(Math.round(controller.toMillis(x2)))
|
||||
.toBeGreaterThan(testEnd);
|
||||
});
|
||||
|
||||
it("provides a width which is not less than scroll area width", function () {
|
||||
var testPixel = mockScope.scroll.width / 4,
|
||||
testMillis = controller.toMillis(testPixel);
|
||||
expect(controller.width(testMillis))
|
||||
.not.toBeLessThan(mockScope.scroll.width);
|
||||
});
|
||||
|
||||
it("provides a width with some margin past timestamp", function () {
|
||||
var testPixel = mockScope.scroll.width * 4,
|
||||
testMillis = controller.toMillis(testPixel);
|
||||
expect(controller.width(testMillis))
|
||||
.toBeGreaterThan(controller.toPixels(testMillis));
|
||||
});
|
||||
|
||||
it("provides a width which does not greatly exceed timestamp", function () {
|
||||
var testPixel = mockScope.scroll.width * 4,
|
||||
testMillis = controller.toMillis(testPixel);
|
||||
expect(controller.width(testMillis))
|
||||
.toBeLessThan(controller.toPixels(testMillis * 2));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -61,8 +61,7 @@ define(
|
||||
{
|
||||
x: event.pageX - rect.left,
|
||||
y: event.pageY - rect.top
|
||||
},
|
||||
domainObject
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -34,8 +34,7 @@ define(
|
||||
TEST_ID = "test-id",
|
||||
DROP_ID = "drop-id";
|
||||
|
||||
//TODO: Disabled for NEM Beta
|
||||
xdescribe("The drop gesture", function () {
|
||||
describe("The drop gesture", function () {
|
||||
var mockDndService,
|
||||
mockQ,
|
||||
mockElement,
|
||||
@ -144,23 +143,6 @@ define(
|
||||
expect(mockCompose.perform).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it("does not invoke compose on drop in browse mode for non-folders", function () {
|
||||
// Set the mockDomainObject to not have the editor capability
|
||||
mockDomainObject.hasCapability.andReturn(false);
|
||||
// Set the mockDomainObject to not have a type of folder
|
||||
mockDomainObject.getModel.andReturn({type: 'notAFolder'});
|
||||
|
||||
callbacks.dragover(mockEvent);
|
||||
expect(mockAction.getActions).toHaveBeenCalledWith({
|
||||
key: 'compose',
|
||||
selectedObject: mockDraggedObject
|
||||
});
|
||||
callbacks.drop(mockEvent);
|
||||
expect(mockCompose.perform).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it("invokes compose on drop in browse mode for folders", function () {
|
||||
// Set the mockDomainObject to not have the editor capability
|
||||
mockDomainObject.hasCapability.andReturn(false);
|
||||
|
@ -34,8 +34,8 @@ var EditItem = (function () {
|
||||
EditItem.prototype.EditButton = function () {
|
||||
return element.all(by.css('[ng-click="parameters.action.perform()"]')).filter(function (arg) {
|
||||
return arg.getAttribute("title").then(function (title){
|
||||
//expect(title).toEqual("Edit this object.");
|
||||
return title == 'Edit this object.';
|
||||
//expect(title).toEqual("Edit");
|
||||
return title == 'Edit';
|
||||
})
|
||||
});
|
||||
};
|
||||
|
Reference in New Issue
Block a user