Merge remote-tracking branch 'origin/open667' into open-master

This commit is contained in:
bwyu
2015-01-16 13:50:53 -08:00
25 changed files with 331 additions and 53 deletions

View File

@@ -2,6 +2,7 @@
"platform/framework", "platform/framework",
"platform/core", "platform/core",
"platform/representation", "platform/representation",
"platform/commonUI/about",
"platform/commonUI/browse", "platform/commonUI/browse",
"platform/commonUI/edit", "platform/commonUI/edit",
"platform/commonUI/dialog", "platform/commonUI/dialog",

View File

@@ -0,0 +1,26 @@
The "about" bundle provides the default lower-right application logo,
as well as the dialog it launches when clicked.
# Extensions
The About dialog contains several line items to display different
version properties (e.g. when built, et cetera.) Plug-ins may wish
to introduce additional line items here, in particular if the
platform is used to build a separately-branded piece of software.
This bundle introduces the `versions` extension category to support this.
An extension of this category is implementation-less (all information
is contained within its declaration) and should include the following
fields:
* `name`: The name to display for this version line-item; this may
be the name of the software, or something else such as "Built".
* `value`: The value to display corresponding to this line-item;
this is typically a version number, revision identifier, or
human-readable date.
* `description`: Optional; a longer-form description of this line
item, to display in a tooltip.
Ordering of these line items is handled by extension priority; see framework
documentation (`platform/framework/README.md`) for information on how
this ordering is handled.

View File

@@ -0,0 +1,43 @@
{
"name": "About Open MCT Web",
"extensions": {
"templates": [
{
"key": "app-logo",
"priority": "optional",
"templateUrl": "templates/app-logo.html"
},
{
"key": "about-logo",
"priority": "preferred",
"templateUrl": "templates/about-logo.html"
},
{
"key": "about-dialog",
"templateUrl": "templates/about-dialog.html"
},
{
"key": "overlay-about",
"templateUrl": "templates/overlay-about.html"
}
],
"controllers": [
{
"key": "LogoController",
"depends": [ "overlayService" ],
"implementation": "LogoController.js"
},
{
"key": "AboutController",
"depends": [ "versions[]", "$window" ],
"implementation": "AboutController.js"
}
],
"routes": [
{
"when": "/licenses",
"templateUrl": "templates/licenses.html"
}
]
}
}

View File

@@ -0,0 +1,12 @@
<div ng-controller = "AboutController as about">
This is a placeholder for the about dialog.
<a ng-click="about.openLicenses()">Show licenses.</a>
<p ng-repeat = "version in about.versions()">
<span title="{{version.description}}">
<b>{{version.name}}</b>
<i>{{version.value}}</i>
</span>
</p>
</div>

View File

@@ -0,0 +1,4 @@
<span ng-controller="LogoController as logo">
<mct-include ng-click="logo.showAboutDialog()" key="'app-logo'">
</mct-include>
</span>

View File

@@ -0,0 +1 @@
<span><div class='app-logo abs'>Open MCT Web</div></span>

View File

@@ -0,0 +1 @@
Placeholder for open source licenses.

View File

@@ -0,0 +1,4 @@
<mct-container key="overlay">
<mct-include key="'about-dialog'">
</mct-include>
</mct-container>

View File

@@ -0,0 +1,42 @@
/*global define*/
define(
[],
function () {
"use strict";
/**
* The AboutController provides information to populate the
* About dialog.
* @constructor
* @param {object[]} versions an array of version extensions;
* injected from `versions[]`
* @param $window Angular-injected window object
*/
function AboutController(versions, $window) {
return {
/**
* Get version info. This is given as an array of
* objects, where each object is intended to appear
* as a line-item in the version information listing.
* @memberof AboutController#
* @returns {object[]} version information
*/
versions: function () {
return versions;
},
/**
* Open a new window (or tab, depending on browser
* configuration) containing open source licenses.
* @memberof AboutController#
*/
openLicenses: function () {
// Open a new browser window at the licenses route
$window.open("#/licenses");
}
};
}
return AboutController;
}
);

View File

@@ -0,0 +1,28 @@
/*global define*/
define(
[],
function () {
"use strict";
/**
* The LogoController provides functionality to the application
* logo in the bottom-right of the user interface.
* @constructor
* @param {OverlayService} overlayService the overlay service
*/
function LogoController(overlayService) {
return {
/**
* Display the About dialog.
* @memberof LogoController#
*/
showAboutDialog: function () {
overlayService.createOverlay("overlay-about");
}
};
}
return LogoController;
}
);

View File

@@ -0,0 +1,39 @@
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
['../src/AboutController'],
function (AboutController) {
"use strict";
describe("The About controller", function () {
var testVersions,
mockWindow,
controller;
beforeEach(function () {
testVersions = [
{ name: "Some name", value: "1.2.3" },
{ name: "Some other name", value: "3.2.1" }
];
mockWindow = jasmine.createSpyObj("$window", ["open"]);
controller = new AboutController(testVersions, mockWindow);
});
it("exposes version information", function () {
// This will be injected, so it should just give back
// what it got in.
expect(controller.versions()).toEqual(testVersions);
});
it("opens license information in a window", function () {
//Verify precondition
expect(mockWindow.open).not.toHaveBeenCalled();
controller.openLicenses();
expect(mockWindow.open).toHaveBeenCalledWith("#/licenses");
});
});
}
);

View File

@@ -0,0 +1,32 @@
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
['../src/LogoController'],
function (LogoController) {
"use strict";
describe("The About controller", function () {
var mockOverlayService,
controller;
beforeEach(function () {
mockOverlayService = jasmine.createSpyObj(
"overlayService",
["createOverlay"]
);
controller = new LogoController(mockOverlayService);
});
it("shows the about dialog", function () {
//Verify precondition
expect(mockOverlayService.createOverlay)
.not.toHaveBeenCalled();
controller.showAboutDialog();
expect(mockOverlayService.createOverlay)
.toHaveBeenCalledWith("overlay-about");
});
});
}
);

View File

@@ -0,0 +1,4 @@
[
"AboutController",
"LogoController"
]

View File

@@ -15,6 +15,16 @@
"templates": [ "templates": [
{ {
"key": "overlay-dialog", "key": "overlay-dialog",
"templateUrl": "templates/overlay-dialog.html"
},
{
"key": "form-dialog",
"templateUrl": "templates/dialog.html"
}
],
"containers": [
{
"key": "overlay",
"templateUrl": "templates/overlay.html" "templateUrl": "templates/overlay.html"
} }
] ]

View File

@@ -0,0 +1,25 @@
<div class="abs top-bar">
<div class="title">{{ngModel.title}}</div>
<div class="hint">
All fields marked <span class="ui-symbol req">*</span> are required.
</div>
</div>
<div class="abs form outline editor">
<div class='abs contents'>
<mct-form ng-model="ngModel.value"
structure="ngModel.structure"
name="createForm">
</mct-form>
</div>
</div>
<div class="abs bottom-bar">
<a class='btn lg major'
href=''
ng-class="{ disabled: !createForm.$valid }"
ng-click="ngModel.confirm()">
OK
</a>
<a class='btn lg subtle' href='' ng-click="ngModel.cancel()">
Cancel
</a>
</div>

View File

@@ -0,0 +1,4 @@
<mct-container key="overlay">
<mct-include key="'form-dialog'" ng-model="ngModel">
</mct-include>
</mct-container>

View File

@@ -3,32 +3,12 @@
<div class="abs holder"> <div class="abs holder">
<a href="" <a href=""
ng-click="ngModel.cancel()" ng-click="ngModel.cancel()"
class="btn normal outline ui-symbol close">x</a> ng-if="ngModel.cancel"
<div class="abs contents"> class="btn normal outline ui-symbol close">
<div class="abs top-bar"> x
<div class="title">{{ngModel.title}}</div>
<div class="hint">All fields marked <span class="ui-symbol req">*</span> are required.</div>
</div>
<div class="abs form outline editor">
<div class='abs contents'>
<!-- mct-include key="'form'" ng-model="" -->
<mct-form ng-model="ngModel.value"
structure="ngModel.structure"
name="createForm">
</mct-form>
</div>
</div>
<div class="abs bottom-bar">
<a class='btn lg major'
href=''
ng-class="{ disabled: !createForm.$valid }"
ng-click="ngModel.confirm()">
OK
</a> </a>
<a class='btn lg subtle' href='' ng-click="ngModel.cancel()"> <div class="abs contents" ng-transclude>
Cancel
</a>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -76,8 +76,8 @@ define(
// Add the overlay using the OverlayService, which // Add the overlay using the OverlayService, which
// will handle actual insertion into the DOM // will handle actual insertion into the DOM
overlay = overlayService.createOverlay( overlay = overlayService.createOverlay(
overlayModel, "overlay-dialog",
"overlay-dialog" overlayModel
); );
// Track that a dialog is already visible, to // Track that a dialog is already visible, to

View File

@@ -25,11 +25,21 @@ define(
* @constructor * @constructor
*/ */
function OverlayService($document, $compile, $rootScope) { function OverlayService($document, $compile, $rootScope) {
function createOverlay(overlayModel, key) { function createOverlay(key, overlayModel) {
// Create a new scope for this overlay // Create a new scope for this overlay
var scope = $rootScope.$new(), var scope = $rootScope.$new(),
element; element;
// Stop showing the overlay; additionally, release the scope
// that it uses.
function dismiss() {
scope.$destroy();
element.remove();
}
// If no model is supplied, just fill in a default "cancel"
overlayModel = overlayModel || { cancel: dismiss };
// Populate the scope; will be passed directly to the template // Populate the scope; will be passed directly to the template
scope.overlay = overlayModel; scope.overlay = overlayModel;
scope.key = key; scope.key = key;
@@ -38,12 +48,7 @@ define(
element = $compile(TEMPLATE)(scope); element = $compile(TEMPLATE)(scope);
$document.find('body').prepend(element); $document.find('body').prepend(element);
// Stop showing the overlay; additionally, release the scope
// that it uses.
function dismiss() {
scope.$destroy();
element.remove();
}
return { return {
dismiss: dismiss dismiss: dismiss
@@ -57,11 +62,12 @@ define(
* template (as pointed to by the `key` argument) is * template (as pointed to by the `key` argument) is
* responsible for having a useful z-order, and for * responsible for having a useful z-order, and for
* blocking user interactions if appropriate. * blocking user interactions if appropriate.
*
* @param {string} key the symbolic key which identifies
* the template of the overlay to be shown
* @param {object} overlayModel the model to pass to the * @param {object} overlayModel the model to pass to the
* included overlay template (this will be passed * included overlay template (this will be passed
* in via ng-model) * in via ng-model)
* @param {string} key the symbolic key which identifies
* the template of the overlay to be shown
*/ */
createOverlay: createOverlay createOverlay: createOverlay
}; };

View File

@@ -56,7 +56,7 @@ define(
it("allows user input to be canceled", function () { it("allows user input to be canceled", function () {
dialogService.getUserInput({}, { someKey: "some value" }); dialogService.getUserInput({}, { someKey: "some value" });
mockOverlayService.createOverlay.mostRecentCall.args[0].cancel(); mockOverlayService.createOverlay.mostRecentCall.args[1].cancel();
expect(mockDeferred.reject).toHaveBeenCalled(); expect(mockDeferred.reject).toHaveBeenCalled();
expect(mockDeferred.resolve).not.toHaveBeenCalled(); expect(mockDeferred.resolve).not.toHaveBeenCalled();
}); });
@@ -64,7 +64,7 @@ define(
it("passes back the result of user input when confirmed", function () { it("passes back the result of user input when confirmed", function () {
var value = { someKey: 42 }; var value = { someKey: 42 };
dialogService.getUserInput({}, value); dialogService.getUserInput({}, value);
mockOverlayService.createOverlay.mostRecentCall.args[0].confirm(); mockOverlayService.createOverlay.mostRecentCall.args[1].confirm();
expect(mockDeferred.reject).not.toHaveBeenCalled(); expect(mockDeferred.reject).not.toHaveBeenCalled();
expect(mockDeferred.resolve).toHaveBeenCalledWith(value); expect(mockDeferred.resolve).toHaveBeenCalledWith(value);
}); });
@@ -80,7 +80,7 @@ define(
it("can show multiple dialogs if prior ones are dismissed", function () { it("can show multiple dialogs if prior ones are dismissed", function () {
dialogService.getUserInput({}, {}); dialogService.getUserInput({}, {});
expect(mockLog.warn).not.toHaveBeenCalled(); expect(mockLog.warn).not.toHaveBeenCalled();
mockOverlayService.createOverlay.mostRecentCall.args[0].confirm(); mockOverlayService.createOverlay.mostRecentCall.args[1].confirm();
dialogService.getUserInput({}, {}); dialogService.getUserInput({}, {});
expect(mockLog.warn).not.toHaveBeenCalled(); expect(mockLog.warn).not.toHaveBeenCalled();
expect(mockDeferred.reject).not.toHaveBeenCalled(); expect(mockDeferred.reject).not.toHaveBeenCalled();

View File

@@ -8,7 +8,7 @@ define(
function (OverlayService) { function (OverlayService) {
"use strict"; "use strict";
describe("The dialog service", function () { describe("The overlay service", function () {
var mockDocument, var mockDocument,
mockCompile, mockCompile,
mockRootScope, mockRootScope,
@@ -40,19 +40,19 @@ define(
}); });
it("prepends an mct-include to create overlays", function () { it("prepends an mct-include to create overlays", function () {
overlayService.createOverlay({}, "test"); overlayService.createOverlay("test", {});
expect(mockCompile).toHaveBeenCalled(); expect(mockCompile).toHaveBeenCalled();
expect(mockCompile.mostRecentCall.args[0].indexOf("mct-include")) expect(mockCompile.mostRecentCall.args[0].indexOf("mct-include"))
.not.toEqual(-1); .not.toEqual(-1);
}); });
it("adds the templated element to the body", function () { it("adds the templated element to the body", function () {
overlayService.createOverlay({}, "test"); overlayService.createOverlay("test", {});
expect(mockBody.prepend).toHaveBeenCalledWith(mockElement); expect(mockBody.prepend).toHaveBeenCalledWith(mockElement);
}); });
it("places the provided model/key in its template's scope", function () { it("places the provided model/key in its template's scope", function () {
overlayService.createOverlay({ someKey: 42 }, "test"); overlayService.createOverlay("test", { someKey: 42 });
expect(mockScope.overlay).toEqual({ someKey: 42 }); expect(mockScope.overlay).toEqual({ someKey: 42 });
expect(mockScope.key).toEqual("test"); expect(mockScope.key).toEqual("test");
@@ -61,7 +61,7 @@ define(
}); });
it("removes the prepended element on request", function () { it("removes the prepended element on request", function () {
var overlay = overlayService.createOverlay({}, "test"); var overlay = overlayService.createOverlay("test", {});
// Verify precondition // Verify precondition
expect(mockElement.remove).not.toHaveBeenCalled(); expect(mockElement.remove).not.toHaveBeenCalled();

View File

@@ -5,5 +5,5 @@
key="indicator.template"> key="indicator.template">
</mct-include> </mct-include>
</div> </div>
<!--mct-include key="'app-logo'"></mct-include--> <mct-include key="'about-logo'"></mct-include>
</div> </div>

View File

@@ -3,6 +3,19 @@
"description": "Defines core concepts of Open MCT Web.", "description": "Defines core concepts of Open MCT Web.",
"sources": "src", "sources": "src",
"extensions": { "extensions": {
"versions": [
{
"name": "Open MCT Web",
"value": "0.3.0-dev",
"priority": 1000
},
{
"name": "Built",
"value": "YYYY-MM-DDTHH:MM:ssZ",
"description": "The date on which this version of the client was built.",
"priority": 990
}
],
"components": [ "components": [
{ {
"provides": "objectService", "provides": "objectService",

View File

@@ -12,5 +12,6 @@
} }
} }
}, },
"extensions": {} "extensions": {
}
} }

View File

@@ -37,12 +37,14 @@ define(
// Prepopulate templateMap for easy look up by key // Prepopulate templateMap for easy look up by key
templates.forEach(function (template) { templates.forEach(function (template) {
var path = [ var key = template.key,
path = [
template.bundle.path, template.bundle.path,
template.bundle.resources, template.bundle.resources,
template.templateUrl template.templateUrl
].join("/"); ].join("/");
templateMap[template.key] = path; // First found should win (priority ordering)
templateMap[key] = templateMap[key] || path;
}); });
function controller($scope) { function controller($scope) {