mirror of
https://github.com/nasa/openmct.git
synced 2025-06-18 07:08:12 +00:00
Merge branch 'open-master' into open962
Merge in latest from open-master into topic branch for WTD-962
This commit is contained in:
@ -108,7 +108,8 @@
|
|||||||
"templateUrl": "templates/items/items.html",
|
"templateUrl": "templates/items/items.html",
|
||||||
"uses": [ "composition" ],
|
"uses": [ "composition" ],
|
||||||
"gestures": [ "drop" ],
|
"gestures": [ "drop" ],
|
||||||
"type": "folder"
|
"type": "folder",
|
||||||
|
"editable": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"components": [
|
"components": [
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "properties",
|
"key": "properties",
|
||||||
"category": "contextual",
|
"category": ["contextual", "view-control"],
|
||||||
"implementation": "actions/PropertiesAction.js",
|
"implementation": "actions/PropertiesAction.js",
|
||||||
"glyph": "p",
|
"glyph": "p",
|
||||||
"name": "Edit Properties...",
|
"name": "Edit Properties...",
|
||||||
@ -79,6 +79,16 @@
|
|||||||
"depends": [ "$location" ]
|
"depends": [ "$location" ]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"category": "action",
|
||||||
|
"implementation": "policies/EditActionPolicy.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "view",
|
||||||
|
"implementation": "policies/EditableViewPolicy.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
"templates": [
|
"templates": [
|
||||||
{
|
{
|
||||||
"key": "edit-library",
|
"key": "edit-library",
|
||||||
|
@ -51,6 +51,11 @@ define(
|
|||||||
// some special behavior for its context capability.
|
// some special behavior for its context capability.
|
||||||
root = root || domainObject;
|
root = root || domainObject;
|
||||||
|
|
||||||
|
// Avoid double-wrapping (WTD-1017)
|
||||||
|
if (domainObject.hasCapability('editor')) {
|
||||||
|
return domainObject;
|
||||||
|
}
|
||||||
|
|
||||||
// Provide an editable form of the object
|
// Provide an editable form of the object
|
||||||
return new EditableDomainObject(
|
return new EditableDomainObject(
|
||||||
domainObject,
|
domainObject,
|
||||||
|
61
platform/commonUI/edit/src/policies/EditActionPolicy.js
Normal file
61
platform/commonUI/edit/src/policies/EditActionPolicy.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy controlling when the `edit` and/or `properties` actions
|
||||||
|
* can appear as applicable actions of the `view-control` category
|
||||||
|
* (shown as buttons in the top-right of browse mode.)
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function EditActionPolicy() {
|
||||||
|
// Get a count of views which are not flagged as non-editable.
|
||||||
|
function countEditableViews(context) {
|
||||||
|
var domainObject = (context || {}).domainObject,
|
||||||
|
views = domainObject && domainObject.useCapability('view'),
|
||||||
|
count = 0;
|
||||||
|
|
||||||
|
// A view is editable unless explicitly flagged as not
|
||||||
|
(views || []).forEach(function (view) {
|
||||||
|
count += (view.editable !== false) ? 1 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Check whether or not a given action is allowed by this
|
||||||
|
* policy.
|
||||||
|
* @param {Action} action the action
|
||||||
|
* @param context the context
|
||||||
|
* @returns {boolean} true if not disallowed
|
||||||
|
*/
|
||||||
|
allow: function (action, context) {
|
||||||
|
var key = action.getMetadata().key,
|
||||||
|
category = (context || {}).category;
|
||||||
|
|
||||||
|
// Only worry about actions in the view-control category
|
||||||
|
if (category === 'view-control') {
|
||||||
|
// Restrict 'edit' to cases where there are editable
|
||||||
|
// views (similarly, restrict 'properties' to when
|
||||||
|
// the converse is true)
|
||||||
|
if (key === 'edit') {
|
||||||
|
return countEditableViews(context) > 0;
|
||||||
|
} else if (key === 'properties') {
|
||||||
|
return countEditableViews(context) < 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like all policies, allow by default.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return EditActionPolicy;
|
||||||
|
}
|
||||||
|
);
|
36
platform/commonUI/edit/src/policies/EditableViewPolicy.js
Normal file
36
platform/commonUI/edit/src/policies/EditableViewPolicy.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy controlling which views should be visible in Edit mode.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function EditableViewPolicy() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Check whether or not a given action is allowed by this
|
||||||
|
* policy.
|
||||||
|
* @param {Action} action the action
|
||||||
|
* @param domainObject the domain object which will be viewed
|
||||||
|
* @returns {boolean} true if not disallowed
|
||||||
|
*/
|
||||||
|
allow: function (view, domainObject) {
|
||||||
|
// If a view is flagged as non-editable, only allow it
|
||||||
|
// while we're not in Edit mode.
|
||||||
|
if ((view || {}).editable === false) {
|
||||||
|
return !domainObject.hasCapability('editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like all policies, allow by default.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return EditableViewPolicy;
|
||||||
|
}
|
||||||
|
);
|
@ -25,7 +25,7 @@ define(
|
|||||||
);
|
);
|
||||||
mockObject = jasmine.createSpyObj(
|
mockObject = jasmine.createSpyObj(
|
||||||
"domainObject",
|
"domainObject",
|
||||||
[ "getId", "getModel", "getCapability" ]
|
[ "getId", "getModel", "getCapability", "hasCapability" ]
|
||||||
);
|
);
|
||||||
mockCapability = jasmine.createSpyObj(
|
mockCapability = jasmine.createSpyObj(
|
||||||
"capability",
|
"capability",
|
||||||
|
@ -21,6 +21,9 @@ define(
|
|||||||
getModel: function () { return {}; },
|
getModel: function () { return {}; },
|
||||||
getCapability: function (name) {
|
getCapability: function (name) {
|
||||||
return completionCapability;
|
return completionCapability;
|
||||||
|
},
|
||||||
|
hasCapability: function (name) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -29,6 +32,9 @@ define(
|
|||||||
var result = Object.create(domainObject);
|
var result = Object.create(domainObject);
|
||||||
result.wrapped = true;
|
result.wrapped = true;
|
||||||
result.wrappedModel = model;
|
result.wrappedModel = model;
|
||||||
|
result.hasCapability = function (name) {
|
||||||
|
return name === 'editor';
|
||||||
|
};
|
||||||
captured.wraps = (captured.wraps || 0) + 1;
|
captured.wraps = (captured.wraps || 0) + 1;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -112,6 +118,19 @@ define(
|
|||||||
expect(cache.isRoot(domainObjects[2])).toBeFalsy();
|
expect(cache.isRoot(domainObjects[2])).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not double-wrap objects", function () {
|
||||||
|
var domainObject = new TestObject('test-id'),
|
||||||
|
wrappedObject = cache.getEditableObject(domainObject);
|
||||||
|
|
||||||
|
// Same instance should be returned if you try to wrap
|
||||||
|
// twice. This is necessary, since it's possible to (e.g.)
|
||||||
|
// use a context capability on an object retrieved via
|
||||||
|
// composition, in which case a result will already be
|
||||||
|
// wrapped.
|
||||||
|
expect(cache.getEditableObject(wrappedObject))
|
||||||
|
.toBe(wrappedObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
78
platform/commonUI/edit/test/policies/EditActionPolicySpec.js
Normal file
78
platform/commonUI/edit/test/policies/EditActionPolicySpec.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*global define,describe,it,expect,beforeEach,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/policies/EditActionPolicy"],
|
||||||
|
function (EditActionPolicy) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The Edit action policy", function () {
|
||||||
|
var editableView,
|
||||||
|
nonEditableView,
|
||||||
|
undefinedView,
|
||||||
|
testViews,
|
||||||
|
testContext,
|
||||||
|
mockDomainObject,
|
||||||
|
mockEditAction,
|
||||||
|
mockPropertiesAction,
|
||||||
|
policy;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
[ 'useCapability' ]
|
||||||
|
);
|
||||||
|
mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||||
|
mockPropertiesAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||||
|
|
||||||
|
editableView = { editable: true };
|
||||||
|
nonEditableView = { editable: false };
|
||||||
|
undefinedView = { someKey: "some value" };
|
||||||
|
testViews = [];
|
||||||
|
|
||||||
|
mockDomainObject.useCapability.andCallFake(function (c) {
|
||||||
|
// Provide test views, only for the view capability
|
||||||
|
return c === 'view' && testViews;
|
||||||
|
});
|
||||||
|
|
||||||
|
mockEditAction.getMetadata.andReturn({ key: 'edit' });
|
||||||
|
mockPropertiesAction.getMetadata.andReturn({ key: 'properties' });
|
||||||
|
|
||||||
|
testContext = {
|
||||||
|
domainObject: mockDomainObject,
|
||||||
|
category: 'view-control'
|
||||||
|
};
|
||||||
|
|
||||||
|
policy = new EditActionPolicy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the edit action when there are editable views", function () {
|
||||||
|
testViews = [ editableView ];
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBeTruthy();
|
||||||
|
// No edit flag defined; should be treated as editable
|
||||||
|
testViews = [ undefinedView, undefinedView ];
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the edit properties action when there are no editable views", function () {
|
||||||
|
testViews = [ nonEditableView, nonEditableView ];
|
||||||
|
expect(policy.allow(mockPropertiesAction, testContext)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows the edit action when there are no editable views", function () {
|
||||||
|
testViews = [ nonEditableView, nonEditableView ];
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows the edit properties action when there are editable views", function () {
|
||||||
|
testViews = [ editableView ];
|
||||||
|
expect(policy.allow(mockPropertiesAction, testContext)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the edit properties outside of the 'view-control' category", function () {
|
||||||
|
testViews = [ nonEditableView ];
|
||||||
|
testContext.category = "something-else";
|
||||||
|
expect(policy.allow(mockPropertiesAction, testContext)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,56 @@
|
|||||||
|
/*global define,describe,it,expect,beforeEach,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/policies/EditableViewPolicy"],
|
||||||
|
function (EditableViewPolicy) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The editable view policy", function () {
|
||||||
|
var testView,
|
||||||
|
mockDomainObject,
|
||||||
|
testMode,
|
||||||
|
policy;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testMode = true; // Act as if we're in Edit mode by default
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
['hasCapability']
|
||||||
|
);
|
||||||
|
mockDomainObject.hasCapability.andCallFake(function (c) {
|
||||||
|
return (c === 'editor') && testMode;
|
||||||
|
});
|
||||||
|
|
||||||
|
policy = new EditableViewPolicy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows views in edit mode that are flagged as non-editable", function () {
|
||||||
|
expect(policy.allow({ editable: false }, mockDomainObject))
|
||||||
|
.toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows views in edit mode that are flagged as editable", function () {
|
||||||
|
expect(policy.allow({ editable: true }, mockDomainObject))
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows any view outside of edit mode", function () {
|
||||||
|
var testViews = [
|
||||||
|
{ editable: false },
|
||||||
|
{ editable: true },
|
||||||
|
{ someKey: "some value" }
|
||||||
|
];
|
||||||
|
testMode = false; // Act as if we're not in Edit mode
|
||||||
|
|
||||||
|
testViews.forEach(function (testView) {
|
||||||
|
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats views with no defined 'editable' property as editable", function () {
|
||||||
|
expect(policy.allow({ someKey: "some value" }, mockDomainObject))
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -18,6 +18,8 @@
|
|||||||
"objects/EditableDomainObject",
|
"objects/EditableDomainObject",
|
||||||
"objects/EditableDomainObjectCache",
|
"objects/EditableDomainObjectCache",
|
||||||
"objects/EditableModelCache",
|
"objects/EditableModelCache",
|
||||||
|
"policies/EditableViewPolicy",
|
||||||
|
"policies/EditActionPolicy",
|
||||||
"representers/EditRepresenter",
|
"representers/EditRepresenter",
|
||||||
"representers/EditToolbar",
|
"representers/EditToolbar",
|
||||||
"representers/EditToolbarRepresenter",
|
"representers/EditToolbarRepresenter",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* CONSTANTS */
|
/* CONSTANTS */
|
||||||
/* line 17, ../../../../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
/* line 17, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
html, body, div, span, applet, object, iframe,
|
html, body, div, span, applet, object, iframe,
|
||||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||||
a, abbr, acronym, address, big, cite, code,
|
a, abbr, acronym, address, big, cite, code,
|
||||||
@ -20,38 +20,38 @@ time, mark, audio, video {
|
|||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
vertical-align: baseline; }
|
vertical-align: baseline; }
|
||||||
|
|
||||||
/* line 22, ../../../../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
/* line 22, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
html {
|
html {
|
||||||
line-height: 1; }
|
line-height: 1; }
|
||||||
|
|
||||||
/* line 24, ../../../../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
/* line 24, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
ol, ul {
|
ol, ul {
|
||||||
list-style: none; }
|
list-style: none; }
|
||||||
|
|
||||||
/* line 26, ../../../../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
/* line 26, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0; }
|
border-spacing: 0; }
|
||||||
|
|
||||||
/* line 28, ../../../../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
/* line 28, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
caption, th, td {
|
caption, th, td {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
vertical-align: middle; }
|
vertical-align: middle; }
|
||||||
|
|
||||||
/* line 30, ../../../../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
/* line 30, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
q, blockquote {
|
q, blockquote {
|
||||||
quotes: none; }
|
quotes: none; }
|
||||||
/* line 103, ../../../../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
/* line 103, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
q:before, q:after, blockquote:before, blockquote:after {
|
q:before, q:after, blockquote:before, blockquote:after {
|
||||||
content: "";
|
content: "";
|
||||||
content: none; }
|
content: none; }
|
||||||
|
|
||||||
/* line 32, ../../../../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
/* line 32, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
a img {
|
a img {
|
||||||
border: none; }
|
border: none; }
|
||||||
|
|
||||||
/* line 116, ../../../../../../../../../../Library/Ruby/Gems/1.8/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
/* line 116, ../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary {
|
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary {
|
||||||
display: block; }
|
display: block; }
|
||||||
|
|
||||||
@ -302,68 +302,80 @@ span {
|
|||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
width: 48.5%; }
|
width: 48.5%; }
|
||||||
/* line 127, ../sass/user-environ/_layout.scss */
|
/* line 127, ../sass/user-environ/_layout.scss */
|
||||||
|
.cols.cols-2-ff .col-100px {
|
||||||
|
width: 100px; }
|
||||||
|
/* line 134, ../sass/user-environ/_layout.scss */
|
||||||
|
.cols.cols-6 .col-1 {
|
||||||
|
min-width: 83.33333px;
|
||||||
|
width: 15.16667%; }
|
||||||
|
/* line 140, ../sass/user-environ/_layout.scss */
|
||||||
.cols.cols-16 .col-1 {
|
.cols.cols-16 .col-1 {
|
||||||
min-width: 31.25px;
|
min-width: 31.25px;
|
||||||
width: 4.75%; }
|
width: 4.75%; }
|
||||||
/* line 130, ../sass/user-environ/_layout.scss */
|
/* line 143, ../sass/user-environ/_layout.scss */
|
||||||
.cols.cols-16 .col-2 {
|
.cols.cols-16 .col-2 {
|
||||||
min-width: 62.5px;
|
min-width: 62.5px;
|
||||||
width: 11%; }
|
width: 11%; }
|
||||||
/* line 133, ../sass/user-environ/_layout.scss */
|
/* line 146, ../sass/user-environ/_layout.scss */
|
||||||
.cols.cols-16 .col-7 {
|
.cols.cols-16 .col-7 {
|
||||||
min-width: 218.75px;
|
min-width: 218.75px;
|
||||||
width: 42.25%; }
|
width: 42.25%; }
|
||||||
/* line 139, ../sass/user-environ/_layout.scss */
|
/* line 152, ../sass/user-environ/_layout.scss */
|
||||||
.cols.cols-32 .col-2 {
|
.cols.cols-32 .col-2 {
|
||||||
min-width: 31.25px;
|
min-width: 31.25px;
|
||||||
width: 4.75%; }
|
width: 4.75%; }
|
||||||
/* line 142, ../sass/user-environ/_layout.scss */
|
/* line 155, ../sass/user-environ/_layout.scss */
|
||||||
.cols.cols-32 .col-15 {
|
.cols.cols-32 .col-15 {
|
||||||
min-width: 234.375px;
|
min-width: 234.375px;
|
||||||
width: 45.375%; }
|
width: 45.375%; }
|
||||||
|
/* line 159, ../sass/user-environ/_layout.scss */
|
||||||
|
.cols .l-row {
|
||||||
|
overflow: hidden;
|
||||||
|
*zoom: 1;
|
||||||
|
padding: 5px 0; }
|
||||||
|
|
||||||
/* line 148, ../sass/user-environ/_layout.scss */
|
/* line 165, ../sass/user-environ/_layout.scss */
|
||||||
.pane {
|
.pane {
|
||||||
position: absolute; }
|
position: absolute; }
|
||||||
/* line 151, ../sass/user-environ/_layout.scss */
|
/* line 168, ../sass/user-environ/_layout.scss */
|
||||||
.pane.treeview .create-btn-holder {
|
.pane.treeview .create-btn-holder {
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
height: 35px; }
|
height: 35px; }
|
||||||
/* line 154, ../sass/user-environ/_layout.scss */
|
/* line 171, ../sass/user-environ/_layout.scss */
|
||||||
.pane.treeview .tree-holder {
|
.pane.treeview .tree-holder {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
top: 40px; }
|
top: 40px; }
|
||||||
/* line 163, ../sass/user-environ/_layout.scss */
|
/* line 180, ../sass/user-environ/_layout.scss */
|
||||||
.pane.items .object-holder {
|
.pane.items .object-holder {
|
||||||
top: 40px; }
|
top: 40px; }
|
||||||
/* line 168, ../sass/user-environ/_layout.scss */
|
/* line 185, ../sass/user-environ/_layout.scss */
|
||||||
.pane.edit-main .object-holder {
|
.pane.edit-main .object-holder {
|
||||||
top: 0; }
|
top: 0; }
|
||||||
/* line 174, ../sass/user-environ/_layout.scss */
|
/* line 191, ../sass/user-environ/_layout.scss */
|
||||||
.pane .object-holder {
|
.pane .object-holder {
|
||||||
overflow: auto; }
|
overflow: auto; }
|
||||||
|
|
||||||
/* line 182, ../sass/user-environ/_layout.scss */
|
/* line 199, ../sass/user-environ/_layout.scss */
|
||||||
.split-layout.horizontal > .pane {
|
.split-layout.horizontal > .pane {
|
||||||
margin-top: 5px; }
|
margin-top: 5px; }
|
||||||
/* line 185, ../sass/user-environ/_layout.scss */
|
/* line 202, ../sass/user-environ/_layout.scss */
|
||||||
.split-layout.horizontal > .pane:first-child {
|
.split-layout.horizontal > .pane:first-child {
|
||||||
margin-top: 0; }
|
margin-top: 0; }
|
||||||
/* line 192, ../sass/user-environ/_layout.scss */
|
/* line 209, ../sass/user-environ/_layout.scss */
|
||||||
.split-layout.vertical > .pane {
|
.split-layout.vertical > .pane {
|
||||||
margin-left: 5px; }
|
margin-left: 5px; }
|
||||||
/* line 194, ../sass/user-environ/_layout.scss */
|
/* line 211, ../sass/user-environ/_layout.scss */
|
||||||
.split-layout.vertical > .pane > .holder {
|
.split-layout.vertical > .pane > .holder {
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0; }
|
right: 0; }
|
||||||
/* line 198, ../sass/user-environ/_layout.scss */
|
/* line 215, ../sass/user-environ/_layout.scss */
|
||||||
.split-layout.vertical > .pane:first-child {
|
.split-layout.vertical > .pane:first-child {
|
||||||
margin-left: 0; }
|
margin-left: 0; }
|
||||||
/* line 200, ../sass/user-environ/_layout.scss */
|
/* line 217, ../sass/user-environ/_layout.scss */
|
||||||
.split-layout.vertical > .pane:first-child .holder {
|
.split-layout.vertical > .pane:first-child .holder {
|
||||||
right: 5px; }
|
right: 5px; }
|
||||||
|
|
||||||
/* line 209, ../sass/user-environ/_layout.scss */
|
/* line 226, ../sass/user-environ/_layout.scss */
|
||||||
.vscroll {
|
.vscroll {
|
||||||
overflow-y: auto; }
|
overflow-y: auto; }
|
||||||
|
|
||||||
@ -2821,10 +2833,10 @@ input[type="text"] {
|
|||||||
.wait-spinner {
|
.wait-spinner {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
-webkit-animation: rotation 0.6s infinite linear;
|
-webkit-animation: rotation .6s infinite linear;
|
||||||
-moz-animation: rotation 0.6s infinite linear;
|
-moz-animation: rotation .6s infinite linear;
|
||||||
-o-animation: rotation 0.6s infinite linear;
|
-o-animation: rotation .6s infinite linear;
|
||||||
animation: rotation 0.6s infinite linear;
|
animation: rotation .6s infinite linear;
|
||||||
border-color: rgba(0, 153, 204, 0.25);
|
border-color: rgba(0, 153, 204, 0.25);
|
||||||
border-top-color: #0099cc;
|
border-top-color: #0099cc;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
@ -2863,10 +2875,10 @@ input[type="text"] {
|
|||||||
.treeview .wait-spinner {
|
.treeview .wait-spinner {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
-webkit-animation: rotation 0.6s infinite linear;
|
-webkit-animation: rotation .6s infinite linear;
|
||||||
-moz-animation: rotation 0.6s infinite linear;
|
-moz-animation: rotation .6s infinite linear;
|
||||||
-o-animation: rotation 0.6s infinite linear;
|
-o-animation: rotation .6s infinite linear;
|
||||||
animation: rotation 0.6s infinite linear;
|
animation: rotation .6s infinite linear;
|
||||||
border-color: rgba(0, 153, 204, 0.25);
|
border-color: rgba(0, 153, 204, 0.25);
|
||||||
border-top-color: #0099cc;
|
border-top-color: #0099cc;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
@ -2879,6 +2891,18 @@ input[type="text"] {
|
|||||||
top: 2px;
|
top: 2px;
|
||||||
left: 0; }
|
left: 0; }
|
||||||
|
|
||||||
|
/* Classes to be used for lists of properties and values */
|
||||||
|
/* line 4, ../sass/_properties.scss */
|
||||||
|
.properties .s-row {
|
||||||
|
border-top: 1px solid #4d4d4d;
|
||||||
|
font-size: 0.8em; }
|
||||||
|
/* line 7, ../sass/_properties.scss */
|
||||||
|
.properties .s-row:first-child {
|
||||||
|
border: none; }
|
||||||
|
/* line 10, ../sass/_properties.scss */
|
||||||
|
.properties .s-row .s-value {
|
||||||
|
color: #fff; }
|
||||||
|
|
||||||
/* line 1, ../sass/_autoflow.scss */
|
/* line 1, ../sass/_autoflow.scss */
|
||||||
.autoflow {
|
.autoflow {
|
||||||
font-size: 0.75rem; }
|
font-size: 0.75rem; }
|
||||||
|
@ -37,4 +37,5 @@
|
|||||||
@import "helpers/bubbles";
|
@import "helpers/bubbles";
|
||||||
@import "helpers/splitter";
|
@import "helpers/splitter";
|
||||||
@import "helpers/wait-spinner";
|
@import "helpers/wait-spinner";
|
||||||
|
@import "properties";
|
||||||
@import "autoflow";
|
@import "autoflow";
|
||||||
|
14
platform/commonUI/general/res/sass/_properties.scss
Normal file
14
platform/commonUI/general/res/sass/_properties.scss
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* Classes to be used for lists of properties and values */
|
||||||
|
|
||||||
|
.properties {
|
||||||
|
.s-row {
|
||||||
|
border-top: 1px solid $colorInteriorBorder;
|
||||||
|
font-size: 0.8em;
|
||||||
|
&:first-child {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.s-value {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -122,6 +122,19 @@
|
|||||||
@include cols($nc, 1);
|
@include cols($nc, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.cols-2-ff {
|
||||||
|
// 2 columns, first column is fixed, second is fluid
|
||||||
|
.col-100px {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.cols-6 {
|
||||||
|
$nc: 6;
|
||||||
|
.col-1 {
|
||||||
|
@include cols($nc, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
&.cols-16 {
|
&.cols-16 {
|
||||||
$nc: 16;
|
$nc: 16;
|
||||||
.col-1 {
|
.col-1 {
|
||||||
@ -143,6 +156,10 @@
|
|||||||
@include cols($nc, 15);
|
@include cols($nc, 15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.l-row {
|
||||||
|
@include clearfix;
|
||||||
|
padding: $interiorMargin 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pane {
|
.pane {
|
||||||
|
@ -84,11 +84,21 @@ define(
|
|||||||
|
|
||||||
// Build up look-up tables
|
// Build up look-up tables
|
||||||
actions.forEach(function (Action) {
|
actions.forEach(function (Action) {
|
||||||
if (Action.category) {
|
// Get an action's category or categories
|
||||||
actionsByCategory[Action.category] =
|
var categories = Action.category || [];
|
||||||
actionsByCategory[Action.category] || [];
|
|
||||||
actionsByCategory[Action.category].push(Action);
|
// Convert to an array if necessary
|
||||||
}
|
categories = Array.isArray(categories) ?
|
||||||
|
categories : [categories];
|
||||||
|
|
||||||
|
// Store action under all relevant categories
|
||||||
|
categories.forEach(function (category) {
|
||||||
|
actionsByCategory[category] =
|
||||||
|
actionsByCategory[category] || [];
|
||||||
|
actionsByCategory[category].push(Action);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store action by ekey as well
|
||||||
if (Action.key) {
|
if (Action.key) {
|
||||||
actionsByKey[Action.key] =
|
actionsByKey[Action.key] =
|
||||||
actionsByKey[Action.key] || [];
|
actionsByKey[Action.key] || [];
|
||||||
|
120
platform/features/plot/src/Canvas2DChart.js
Normal file
120
platform/features/plot/src/Canvas2DChart.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*global define,Float32Array*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new chart which uses Canvas's 2D API for rendering.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {CanvasElement} canvas the canvas object to render upon
|
||||||
|
* @throws {Error} an error is thrown if Canvas's 2D API is unavailable.
|
||||||
|
*/
|
||||||
|
function Canvas2DChart(canvas) {
|
||||||
|
var c2d = canvas.getContext('2d'),
|
||||||
|
width = canvas.width,
|
||||||
|
height = canvas.height,
|
||||||
|
dimensions = [ width, height ],
|
||||||
|
origin = [ 0, 0 ];
|
||||||
|
|
||||||
|
// Convert from logical to physical x coordinates
|
||||||
|
function x(v) {
|
||||||
|
return ((v - origin[0]) / dimensions[0]) * width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from logical to physical y coordinates
|
||||||
|
function y(v) {
|
||||||
|
return height - ((v - origin[1]) / dimensions[1]) * height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the color to be used for drawing operations
|
||||||
|
function setColor(color) {
|
||||||
|
var mappedColor = color.map(function (c, i) {
|
||||||
|
return i < 3 ? Math.floor(c * 255) : (c);
|
||||||
|
}).join(',');
|
||||||
|
c2d.strokeStyle = "rgba(" + mappedColor + ")";
|
||||||
|
c2d.fillStyle = "rgba(" + mappedColor + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!c2d) {
|
||||||
|
throw new Error("Canvas 2d API unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Clear the chart.
|
||||||
|
*/
|
||||||
|
clear: function () {
|
||||||
|
width = canvas.width;
|
||||||
|
height = canvas.height;
|
||||||
|
c2d.clearRect(0, 0, width, height);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Set the logical boundaries of the chart.
|
||||||
|
* @param {number[]} dimensions the horizontal and
|
||||||
|
* vertical dimensions of the chart
|
||||||
|
* @param {number[]} origin the horizontal/vertical
|
||||||
|
* origin of the chart
|
||||||
|
*/
|
||||||
|
setDimensions: function (newDimensions, newOrigin) {
|
||||||
|
dimensions = newDimensions;
|
||||||
|
origin = newOrigin;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Draw the supplied buffer as a line strip (a sequence
|
||||||
|
* of line segments), in the chosen color.
|
||||||
|
* @param {Float32Array} buf the line strip to draw,
|
||||||
|
* in alternating x/y positions
|
||||||
|
* @param {number[]} color the color to use when drawing
|
||||||
|
* the line, as an RGBA color where each element
|
||||||
|
* is in the range of 0.0-1.0
|
||||||
|
* @param {number} points the number of points to draw
|
||||||
|
*/
|
||||||
|
drawLine: function (buf, color, points) {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
setColor(color);
|
||||||
|
|
||||||
|
// Configure context to draw two-pixel-thick lines
|
||||||
|
c2d.lineWidth = 2;
|
||||||
|
|
||||||
|
// Start a new path...
|
||||||
|
if (buf.length > 1) {
|
||||||
|
c2d.beginPath();
|
||||||
|
c2d.moveTo(x(buf[0]), y(buf[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...and add points to it...
|
||||||
|
for (i = 2; i < points * 2; i = i + 2) {
|
||||||
|
c2d.lineTo(x(buf[i]), y(buf[i + 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...before finally drawing it.
|
||||||
|
c2d.stroke();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Draw a rectangle extending from one corner to another,
|
||||||
|
* in the chosen color.
|
||||||
|
* @param {number[]} min the first corner of the rectangle
|
||||||
|
* @param {number[]} max the opposite corner
|
||||||
|
* @param {number[]} color the color to use when drawing
|
||||||
|
* the rectangle, as an RGBA color where each element
|
||||||
|
* is in the range of 0.0-1.0
|
||||||
|
*/
|
||||||
|
drawSquare: function (min, max, color) {
|
||||||
|
var x1 = x(min[0]),
|
||||||
|
y1 = y(min[1]),
|
||||||
|
w = x(max[0]) - x1,
|
||||||
|
h = y(max[1]) - y1;
|
||||||
|
|
||||||
|
setColor(color);
|
||||||
|
c2d.fillRect(x1, y1, w, h);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Canvas2DChart;
|
||||||
|
}
|
||||||
|
);
|
@ -4,8 +4,8 @@
|
|||||||
* Module defining MCTChart. Created by vwoeltje on 11/12/14.
|
* Module defining MCTChart. Created by vwoeltje on 11/12/14.
|
||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
["./GLChart"],
|
["./GLChart", "./Canvas2DChart"],
|
||||||
function (GLChart) {
|
function (GLChart, Canvas2DChart) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var TEMPLATE = "<canvas style='position: absolute; background: none; width: 100%; height: 100%;'></canvas>";
|
var TEMPLATE = "<canvas style='position: absolute; background: none; width: 100%; height: 100%;'></canvas>";
|
||||||
@ -43,22 +43,38 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function MCTChart($interval, $log) {
|
function MCTChart($interval, $log) {
|
||||||
|
// Get an underlying chart implementation
|
||||||
|
function getChart(Charts, canvas) {
|
||||||
|
// Try the first available option...
|
||||||
|
var Chart = Charts[0];
|
||||||
|
|
||||||
|
// This function recursively try-catches all options;
|
||||||
|
// if these all fail, issue a warning.
|
||||||
|
if (!Chart) {
|
||||||
|
$log.warn("Cannot initialize mct-chart.");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try first option; if it fails, try remaining options
|
||||||
|
try {
|
||||||
|
return new Chart(canvas);
|
||||||
|
} catch (e) {
|
||||||
|
$log.warn([
|
||||||
|
"Could not instantiate chart",
|
||||||
|
Chart.name,
|
||||||
|
";",
|
||||||
|
e.message
|
||||||
|
].join(" "));
|
||||||
|
|
||||||
|
return getChart(Charts.slice(1), canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function linkChart(scope, element) {
|
function linkChart(scope, element) {
|
||||||
var canvas = element.find("canvas")[0],
|
var canvas = element.find("canvas")[0],
|
||||||
activeInterval,
|
activeInterval,
|
||||||
chart;
|
chart;
|
||||||
|
|
||||||
// Try to initialize GLChart, which allows drawing using WebGL.
|
|
||||||
// This may fail, particularly where browsers do not support
|
|
||||||
// WebGL, so catch that here.
|
|
||||||
try {
|
|
||||||
chart = new GLChart(canvas);
|
|
||||||
} catch (e) {
|
|
||||||
$log.warn("Cannot initialize mct-chart; " + e.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle drawing, based on contents of the "draw" object
|
// Handle drawing, based on contents of the "draw" object
|
||||||
// in scope
|
// in scope
|
||||||
function doDraw(draw) {
|
function doDraw(draw) {
|
||||||
@ -118,6 +134,15 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to initialize a chart.
|
||||||
|
chart = getChart([GLChart, Canvas2DChart], canvas);
|
||||||
|
|
||||||
|
// If that failed, there's nothing more we can do here.
|
||||||
|
// (A warning will already have been issued)
|
||||||
|
if (!chart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for resize, on a timer
|
// Check for resize, on a timer
|
||||||
activeInterval = $interval(drawIfResized, 1000);
|
activeInterval = $interval(drawIfResized, 1000);
|
||||||
|
|
||||||
|
76
platform/features/plot/test/Canvas2DChartSpec.js
Normal file
76
platform/features/plot/test/Canvas2DChartSpec.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/Canvas2DChart"],
|
||||||
|
function (Canvas2DChart) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("A canvas 2d chart", function () {
|
||||||
|
var mockCanvas,
|
||||||
|
mock2d,
|
||||||
|
chart;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockCanvas = jasmine.createSpyObj("canvas", [ "getContext" ]);
|
||||||
|
mock2d = jasmine.createSpyObj(
|
||||||
|
"2d",
|
||||||
|
[
|
||||||
|
"clearRect",
|
||||||
|
"beginPath",
|
||||||
|
"moveTo",
|
||||||
|
"lineTo",
|
||||||
|
"stroke",
|
||||||
|
"fillRect"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockCanvas.getContext.andReturn(mock2d);
|
||||||
|
|
||||||
|
chart = new Canvas2DChart(mockCanvas);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Note that tests below are less specific than they
|
||||||
|
// could be, esp. w.r.t. arguments to drawing calls;
|
||||||
|
// this is a fallback option so is a lower test priority.
|
||||||
|
|
||||||
|
it("allows the canvas to be cleared", function () {
|
||||||
|
chart.clear();
|
||||||
|
expect(mock2d.clearRect).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doees not construct if 2D is unavailable", function () {
|
||||||
|
mockCanvas.getContext.andReturn(undefined);
|
||||||
|
expect(function () {
|
||||||
|
return new Canvas2DChart(mockCanvas);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows dimensions to be set", function () {
|
||||||
|
// No return value, just verify API is present
|
||||||
|
chart.setDimensions([120, 120], [0, 10]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows lines to be drawn", function () {
|
||||||
|
var testBuffer = [ 0, 1, 3, 8 ],
|
||||||
|
testColor = [ 0.25, 0.33, 0.66, 1.0 ],
|
||||||
|
testPoints = 2;
|
||||||
|
chart.drawLine(testBuffer, testColor, testPoints);
|
||||||
|
expect(mock2d.beginPath).toHaveBeenCalled();
|
||||||
|
expect(mock2d.lineTo.calls.length).toEqual(1);
|
||||||
|
expect(mock2d.stroke).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows squares to be drawn", function () {
|
||||||
|
var testMin = [0, 1],
|
||||||
|
testMax = [10, 10],
|
||||||
|
testColor = [ 0.25, 0.33, 0.66, 1.0 ];
|
||||||
|
|
||||||
|
chart.drawSquare(testMin, testMax, testColor);
|
||||||
|
expect(mock2d.fillRect).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -1,4 +1,5 @@
|
|||||||
[
|
[
|
||||||
|
"Canvas2DChart",
|
||||||
"GLChart",
|
"GLChart",
|
||||||
"MCTChart",
|
"MCTChart",
|
||||||
"PlotController",
|
"PlotController",
|
||||||
|
@ -10,6 +10,12 @@
|
|||||||
"implementation": "PolicyActionDecorator.js",
|
"implementation": "PolicyActionDecorator.js",
|
||||||
"depends": [ "policyService" ]
|
"depends": [ "policyService" ]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "decorator",
|
||||||
|
"provides": "viewService",
|
||||||
|
"implementation": "PolicyViewDecorator.js",
|
||||||
|
"depends": [ "policyService" ]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "provider",
|
"type": "provider",
|
||||||
"provides": "policyService",
|
"provides": "policyService",
|
||||||
|
@ -15,7 +15,7 @@ define(
|
|||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Get actions which are applicable in this context.
|
* Get actions which are applicable in this context.
|
||||||
* These will be filters to remove any actions which
|
* These will be filtered to remove any actions which
|
||||||
* are deemed inapplicable by policy.
|
* are deemed inapplicable by policy.
|
||||||
* @param context the context in which the action will occur
|
* @param context the context in which the action will occur
|
||||||
* @returns {Action[]} applicable actions
|
* @returns {Action[]} applicable actions
|
||||||
|
37
platform/policy/src/PolicyViewDecorator.js
Normal file
37
platform/policy/src/PolicyViewDecorator.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters out views based on policy.
|
||||||
|
* @param {PolicyService} policyService the service which provides
|
||||||
|
* policy decisions
|
||||||
|
* @param {ViewService} viewService the service to decorate
|
||||||
|
*/
|
||||||
|
function PolicyActionDecorator(policyService, viewService) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get views which are applicable to this domain object.
|
||||||
|
* These will be filtered to remove any views which
|
||||||
|
* are deemed inapplicable by policy.
|
||||||
|
* @param {DomainObject} the domain object to view
|
||||||
|
* @returns {View[]} applicable views
|
||||||
|
*/
|
||||||
|
getViews: function (domainObject) {
|
||||||
|
// Check if an action is allowed by policy.
|
||||||
|
function allow(view) {
|
||||||
|
return policyService.allow('view', view, domainObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up actions, filter out the disallowed ones.
|
||||||
|
return viewService.getViews(domainObject).filter(allow);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return PolicyActionDecorator;
|
||||||
|
}
|
||||||
|
);
|
83
platform/policy/test/PolicyViewDecoratorSpec.js
Normal file
83
platform/policy/test/PolicyViewDecoratorSpec.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../src/PolicyViewDecorator"],
|
||||||
|
function (PolicyViewDecorator) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The policy view decorator", function () {
|
||||||
|
var mockPolicyService,
|
||||||
|
mockViewService,
|
||||||
|
mockDomainObject,
|
||||||
|
testViews,
|
||||||
|
decorator;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockPolicyService = jasmine.createSpyObj(
|
||||||
|
'policyService',
|
||||||
|
['allow']
|
||||||
|
);
|
||||||
|
mockViewService = jasmine.createSpyObj(
|
||||||
|
'viewService',
|
||||||
|
['getViews']
|
||||||
|
);
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
['getId']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Content of actions should be irrelevant to this
|
||||||
|
// decorator, so just give it some objects to pass
|
||||||
|
// around.
|
||||||
|
testViews = [
|
||||||
|
{ someKey: "a" },
|
||||||
|
{ someKey: "b" },
|
||||||
|
{ someKey: "c" }
|
||||||
|
];
|
||||||
|
|
||||||
|
mockDomainObject.getId.andReturn('xyz');
|
||||||
|
mockViewService.getViews.andReturn(testViews);
|
||||||
|
mockPolicyService.allow.andReturn(true);
|
||||||
|
|
||||||
|
decorator = new PolicyViewDecorator(
|
||||||
|
mockPolicyService,
|
||||||
|
mockViewService
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("delegates to its decorated view service", function () {
|
||||||
|
decorator.getViews(mockDomainObject);
|
||||||
|
expect(mockViewService.getViews)
|
||||||
|
.toHaveBeenCalledWith(mockDomainObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides views from its decorated view service", function () {
|
||||||
|
// Mock policy service allows everything by default,
|
||||||
|
// so everything should be returned
|
||||||
|
expect(decorator.getViews(mockDomainObject))
|
||||||
|
.toEqual(testViews);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("consults the policy service for each candidate view", function () {
|
||||||
|
decorator.getViews(mockDomainObject);
|
||||||
|
testViews.forEach(function (testView) {
|
||||||
|
expect(mockPolicyService.allow).toHaveBeenCalledWith(
|
||||||
|
'view',
|
||||||
|
testView,
|
||||||
|
mockDomainObject
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("filters out policy-disallowed views", function () {
|
||||||
|
// Disallow the second action
|
||||||
|
mockPolicyService.allow.andCallFake(function (cat, candidate, ctxt) {
|
||||||
|
return candidate.someKey !== 'b';
|
||||||
|
});
|
||||||
|
expect(decorator.getViews(mockDomainObject))
|
||||||
|
.toEqual([ testViews[0], testViews[2] ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -1,4 +1,5 @@
|
|||||||
[
|
[
|
||||||
"PolicyActionDecorator",
|
"PolicyActionDecorator",
|
||||||
|
"PolicyViewDecorator",
|
||||||
"PolicyProvider"
|
"PolicyProvider"
|
||||||
]
|
]
|
Reference in New Issue
Block a user