[Common UI] Initial commonUI bundles

Bring in work on general-purpose and over-arching
user interface bundles from the sandbox transition
branch. WTD-574.
This commit is contained in:
Victor Woeltjen
2014-11-23 15:41:20 -08:00
parent 0cd331e8a5
commit 1b0303e517
73 changed files with 6035 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
{
"extensions": {
"routes": [
{
"when": "/browse",
"templateUrl": "templates/browse.html"
},
{
"when": "",
"templateUrl": "templates/browse.html"
}
],
"controllers": [
{
"key": "BrowseController",
"implementation": "BrowseController.js",
"depends": [ "$scope", "objectService", "navigationService" ]
},
{
"key": "ViewSwitcherController",
"implementation": "ViewSwitcherController.js",
"depends": [ "$scope" ]
},
{
"key": "CreateButtonController",
"implementation": "creation/CreateButtonController",
"depends": [ "$scope", "$document" ]
},
{
"key": "CreateMenuController",
"implementation": "creation/CreateMenuController",
"depends": [ "$scope" ]
}
],
"templates": [
{
"key": "topbar-browse",
"templateUrl": "templates/topbar-browse.html"
}
],
"representations": [
{
"key": "browse-object",
"templateUrl": "templates/browse-object.html",
"uses": [ "view" ]
},
{
"key": "create-button",
"templateUrl": "templates/create-button.html"
},
{
"key": "create-menu",
"templateUrl": "templates/create-menu.html",
"uses": [ "action" ]
},
{
"key": "grid-item",
"templateUrl": "templates/items/grid-item.html",
"uses": [ "type", "action" ]
},
{
"key": "object-header",
"templateUrl": "templates/browse/object-header.html",
"uses": [ "type" ]
}
],
"services": [
{
"key": "navigationService",
"implementation": "navigation/NavigationService.js"
},
{
"key": "creationService",
"implementation": "creation/CreationService.js",
"depends": [ "persistenceService", "uuidService", "$q", "$log" ]
},
{
"key": "uuidService",
"implementation": "creation/UUIDService.js"
}
],
"actions": [
{
"key": "navigate",
"implementation": "navigation/NavigateAction.js",
"depends": [ "navigationService" ]
},
{
"key": "window",
"implementation": "windowing/NewWindowAction.js",
"description": "Open this object in a new window.",
"category": "view-control",
"depends": [ "$window" ],
"group": "windowing",
"glyph": "y"
},
{
"key": "fullscreen",
"implementation": "windowing/FullscreenAction.js",
"category": "view-control",
"group": "windowing",
"glyph": "z"
}
],
"views": [
{
"key": "items",
"name": "Items",
"glyph": "i",
"description": "Grid of available items.",
"templateUrl": "templates/items/items.html",
"uses": [ "composition" ],
"gestures": [ "drop" ]
}
],
"components": [
{
"key": "CreateActionProvider",
"provides": "actionService",
"type": "provider",
"implementation": "creation/CreateActionProvider.js",
"depends": [ "typeService", "dialogService", "creationService" ]
}
]
}
}

View File

@@ -0,0 +1,6 @@
/*!
* screenfull
* v1.2.0 - 2014-04-29
* (c) Sindre Sorhus; MIT License
*/
!function(){"use strict";var a="undefined"!=typeof module&&module.exports,b="undefined"!=typeof Element&&"ALLOW_KEYBOARD_INPUT"in Element,c=function(){for(var a,b,c=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],d=0,e=c.length,f={};e>d;d++)if(a=c[d],a&&a[1]in document){for(d=0,b=a.length;b>d;d++)f[c[0][d]]=a[d];return f}return!1}(),d={request:function(a){var d=c.requestFullscreen;a=a||document.documentElement,/5\.1[\.\d]* Safari/.test(navigator.userAgent)?a[d]():a[d](b&&Element.ALLOW_KEYBOARD_INPUT)},exit:function(){document[c.exitFullscreen]()},toggle:function(a){this.isFullscreen?this.exit():this.request(a)},onchange:function(){},onerror:function(){},raw:c};return c?(Object.defineProperties(d,{isFullscreen:{get:function(){return!!document[c.fullscreenElement]}},element:{enumerable:!0,get:function(){return document[c.fullscreenElement]}},enabled:{enumerable:!0,get:function(){return!!document[c.fullscreenEnabled]}}}),document.addEventListener(c.fullscreenchange,function(a){d.onchange.call(d,a)}),document.addEventListener(c.fullscreenerror,function(a){d.onerror.call(d,a)}),void(a?module.exports=d:window.screenfull=d)):void(a?module.exports=!1:window.screenfull=!1)}();

View File

@@ -0,0 +1,26 @@
<span ng-controller="ViewSwitcherController">
<div class="object-browse-bar bar abs">
<div class="items-select left abs">
<mct-representation key="'object-header'" mct-object="domainObject">
</mct-representation>
</div>
<div class="view-controls sort-controls btn-bar right abs">
<mct-representation key="'action-group'"
mct-object="domainObject"
parameters="{ category: 'view-control' }">
</mct-representation>
<mct-include key="'switcher'" ng-model="switcher" ng-if="switcher.options.length > 0">
</mct-include>
</div>
</div>
<div class='object-holder abs vscroll'>
<mct-representation key="switcher.selected.key" mct-object="domainObject">
</mct-representation>
</div>
</span>

View File

@@ -0,0 +1,25 @@
<div content="jquery-wrapper" class="abs holder-all browse-mode">
<mct-include key="'topbar-browse'"></mct-include>
<div class="holder browse-area outline abs" ng-controller="BrowseController as browse">
<div class='split-layout vertical contents abs'>
<div class='split-pane-component treeview pane' style="width: 200px;">
<mct-representation key="'create-button'" mct-object="navigatedObject">
</mct-representation>
<div class='holder tree-holder abs'>
<mct-representation key="'tree'"
mct-object="domainObject"
parameters="{callback: browse.setNavigation}">
</mct-representation>
</div>
</div>
<div class="splitter" style="left: 200px"></div>
<div class='split-pane-component items pane' style="right:0; left:200px;">
<div class='holder abs' id='content-area'>
<mct-representation mct-object="navigatedObject" key="'browse-object'">
</mct-representation>
</div>
</div>
</div>
</div>
<mct-include key="'bottombar'"></mct-include>
</div>

View File

@@ -0,0 +1,7 @@
<div class='object-header'>
<span class='type-icon icon ui-symbol'>{{type.getGlyph()}}</span>
<span class='action'>{{parameters.mode}}</span>
<span class='type'>{{type.getName()}}</span>
<span class='title'>{{model.name}}</span>
<a id='actions-menu' class='ui-symbol invoke-menu' onclick="alert('Not yet functional. This will display a dropdown menu of options for this object.');">v</a>
</div>

View File

@@ -0,0 +1,9 @@
<div class="menu-element wrapper" ng-controller="CreateButtonController">
<div class="btn btn-menu create-btn major" ng-click="toggle()">
<span class='ui-symbol major' href=''>+</span> Create<!--span class='ui-symbol invoke-menu'>v</span-->
</div>
<div class="menu dropdown super-menu" ng-show="createState.visible">
<mct-representation mct-object="domainObject" key="'create-menu'">
</mct-representation>
</div>
</div>

View File

@@ -0,0 +1,30 @@
<div class="contents" ng-controller="CreateMenuController">
<div class="pane left menu-items">
<ul>
<li ng-repeat="createAction in createActions">
<a href=''
ng-click="createAction.perform()"
ng-mouseover="representation.activeMetadata = createAction.getMetadata()"
ng-mouseleave="representation.activeMetadata = undefined">
<span class="ui-symbol icon type-icon">
{{createAction.getMetadata().glyph}}
</span>
{{createAction.getMetadata().name}}
</a>
</li>
</ul>
</div>
<div class="pane right menu-item-description">
<div class="desc-area ui-symbol icon type-icon">
{{representation.activeMetadata.glyph}}
</div>
<div class="desc-area title">
{{representation.activeMetadata.name}}
</div>
<div class="desc-area description">
{{representation.activeMetadata.description}}
</div>
</div>
</div>

View File

@@ -0,0 +1,26 @@
<!-- For selected, add class 'selected' to outer div -->
<div class='item grid-item' ng-click='action.perform("navigate")'>
<div class="contents abs">
<div class='top-bar bar abs'>
<div class='left abs'>
<mct-include key="_checkbox"></mct-include>
</div>
<div class='right abs'>
<div class='ui-symbol icon alert hidden' onclick="alert('Not yet functional. When this is visible, it means that this object needs to be updated. Clicking will allow that action via a dialog.');">!</div>
<div class='ui-symbol icon profile' onclick="alert('Not yet functional. This will allow sharing and permissions to be controlled for this object.');">P</div>
</div>
</div>
<div class='item-main abs'>
<div class='ui-symbol icon lg abs item-type'>{{type.getGlyph()}}</div>
<div class='ui-symbol icon abs item-open'>}</div>
</div>
<div class='bottom-bar bar abs'>
<div class='title'>{{model.name}}</div>
<div class='details'>
<span ng-show="model.composition !== undefined">
{{model.composition.length}} Items
</span>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,6 @@
<div class='items-holder grid abs'>
<mct-representation key="'grid-item'"
ng-repeat="childObject in composition"
mct-object="childObject">
</mct-representation>
</div>

View File

@@ -0,0 +1,15 @@
<div class='top-bar browse abs'>
<!-- TO-DO: replace JS placeholders for click actions -->
<div class='browse-main bar abs left'>
<a class="menu-element btn btn-menu browse-btn" onclick="alert('Not yet functional. This will allow filtering of browsed objects and search context.');">
<span class='ui-symbol badge major' href=''>*</span>Browse All<span class='ui-symbol invoke-menu'>v</span>
</a>
<input type='text' class='control filter' name='filter-available'/>
<a class='icon icon-filter ui-symbol' onclick="alert('Not yet functional. This will initiate a search.');">M</a>
</div>
<div class='icon-buttons-main bar abs right'>
<a class='ui-symbol icon major alert' onclick="alert('Not yet functional. This will allow updating of domain objects that need to be refreshed.');">!<span id='alert-actions-menu' class='ui-symbol invoke-menu'>v</span></a>
<!--a class='ui-symbol icon major profile' href=''>P<span id='profile-actions-menu' class='ui-symbol invoke-menu'>v</span></a-->
<a class='ui-symbol icon major settings' onclick="alert('Not yet functional. This will allow access to application configuration settings.');">G<span id='settings-actions-menu' class='ui-symbol invoke-menu'>v</span></a>
</div>
</div>

View File

@@ -0,0 +1,53 @@
/*global define,Promise*/
/**
* Module defining BrowseController. Created by vwoeltje on 11/7/14.
*/
define(
[],
function () {
"use strict";
var ROOT_OBJECT = "ROOT";
/**
*
* @constructor
*/
function BrowseController($scope, objectService, navigationService) {
function setNavigation(domainObject) {
$scope.navigatedObject = domainObject;
//$scope.$apply("navigatedObject");
}
objectService.getObjects([ROOT_OBJECT]).then(function (objects) {
var composition = objects[ROOT_OBJECT].useCapability("composition");
$scope.domainObject = objects[ROOT_OBJECT];
if (composition) {
composition.then(function (c) {
// Navigate to the last root level component (usually "mine")
if (!navigationService.getNavigation()) {
navigationService.setNavigation(c[c.length - 1]);
} else {
$scope.navigatedObject = navigationService.getNavigation();
}
});
}
});
$scope.$on("$destroy", function () {
navigationService.removeListener(setNavigation);
});
navigationService.addListener(setNavigation);
return {
setNavigation: function (domainObject) {
navigationService.setNavigation(domainObject);
}
};
}
return BrowseController;
}
);

View File

@@ -0,0 +1,48 @@
/*global define,Promise*/
/**
* Module defining ViewSwitcherController. Created by vwoeltje on 11/7/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function ViewSwitcherController($scope) {
// If the view capability gets refreshed, try to
// keep the same option chosen.
function findMatchingOption(options, selected) {
var i;
if (selected) {
for (i = 0; i < options.length; i += 1) {
if (options[i].key === selected.key) {
return options[i];
}
}
}
return options[0];
}
// Get list of views, read from capability
$scope.$watch("view", function () {
var options = $scope.view || [ {} ];
$scope.switcher = {
options: options,
selected: findMatchingOption(
options,
($scope.switcher || {}).selected
)
};
});
}
return ViewSwitcherController;
}
);

View File

@@ -0,0 +1,67 @@
/*global define,Promise*/
/**
* Module defining CreateAction. Created by vwoeltje on 11/10/14.
*/
define(
['./CreateWizard'],
function (CreateWizard) {
"use strict";
/**
*
* @constructor
*/
function CreateAction(type, parent, context, dialogService, creationService) {
/*
1. Show dialog
a. Prepare dialog contents
b. Invoke dialogService
2. Create new object in persistence service
a. Generate UUID
b. Store model
3. Mutate destination container
a. Get mutation capability
b. Add new id to composition
4. Persist destination container
a. ...use persistence capability.
*/
function perform() {
var wizard = new CreateWizard(type, parent);
function persistResult(formValue) {
var parent = wizard.getLocation(formValue),
newModel = wizard.createModel(formValue);
return creationService.createObject(newModel, parent);
}
function doNothing() {
// Create cancelled, do nothing
return false;
}
return dialogService.getUserInput(
wizard.getFormModel()
).then(persistResult, doNothing);
}
return {
perform: perform,
getMetadata: function () {
return {
key: 'create',
glyph: type.getGlyph(),
name: type.getName(),
description: type.getDescription(),
context: context
};
}
};
}
return CreateAction;
}
);

View File

@@ -0,0 +1,41 @@
/*global define,Promise*/
/**
* Module defining CreateActionProvider.js. Created by vwoeltje on 11/10/14.
*/
define(
["./CreateAction"],
function (CreateAction) {
"use strict";
/**
*
* @constructor
*/
function CreateActionProvider(typeService, dialogService, creationService) {
return {
getActions: function (actionContext) {
var context = actionContext || {},
key = context.key,
destination = context.domainObject;
if (key !== 'create' || !destination) {
return [];
}
return typeService.listTypes().map(function (type) {
return new CreateAction(
type,
destination,
context,
dialogService,
creationService
);
});
}
};
}
return CreateActionProvider;
}
);

View File

@@ -0,0 +1,36 @@
/*global define,Promise*/
/**
* Module defining CreateController. Created by vwoeltje on 11/10/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function CreateButtonController($scope, $document) {
function collapse() {
$scope.createState.visible = false;
$scope.$apply("createState.visible");
$document.off("mouseup", collapse);
return false;
}
$scope.createState = { visible: false };
$scope.toggle = function () {
$scope.createState.visible = !$scope.createState.visible;
if ($scope.createState.visible) {
$document.on("mouseup", collapse);
}
};
}
return CreateButtonController;
}
);

View File

@@ -0,0 +1,29 @@
/*global define,Promise*/
/**
* Module defining CreateMenuController. Created by vwoeltje on 11/10/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function CreateMenuController($scope) {
function refreshActions() {
var actionCapability = $scope.action;
if (actionCapability) {
$scope.createActions =
actionCapability.getActions('create');
}
}
$scope.$watch("action", refreshActions);
}
return CreateMenuController;
}
);

View File

@@ -0,0 +1,79 @@
/*global define*/
/**
* Defines the CreateWizard, used by the CreateAction to
* populate the form shown in dialog based on the created type.
*
* @module core/action/create-wizard
*/
define(
function () {
'use strict';
/**
* Construct a new CreateWizard.
*
* @param {TypeImpl} type the type of domain object to be created
* @param {DomainObject} parent the domain object to serve as
* the initial parent for the created object, in the dialog
* @constructor
* @memberof module:core/action/create-wizard
*/
function CreateWizard(type, parent) {
var model = type.getInitialModel(),
properties = type.getProperties();
return {
getFormModel: function () {
var parentRow = Object.create(parent),
sections = [];
sections.push({
name: "Properties",
rows: properties.map(function (property) {
// Property definition is same as form row definition
var row = Object.create(property.getDefinition());
// But pull an initial value from the model
row.value = property.getValue(model);
return row;
})
});
// Ensure there is always a "save in" section
parentRow.name = "Save In";
parentRow.cssclass = "selector-list";
parentRow.control = "_locator";
parentRow.key = "createParent";
sections.push({ label: 'Location', rows: [parentRow]});
return {
sections: sections,
name: "Create a New " + type.getName()
};
},
getLocation: function (formValue) {
return formValue.createParent || parent;
},
createModel: function (formValue) {
// Clone
var newModel = JSON.parse(JSON.stringify(model));
// Always use the type from the type definition
newModel.type = type.getKey();
// Update all properties
properties.forEach(function (property) {
var value = formValue[property.getDefinition().key];
property.setValue(newModel, value);
});
return newModel;
}
};
}
return CreateWizard;
}
);

View File

@@ -0,0 +1,90 @@
/*global define,Promise*/
/**
* Module defining CreateService. Created by vwoeltje on 11/10/14.
*/
define(
[],
function () {
"use strict";
var NON_PERSISTENT_WARNING =
"Tried to create an object in non-persistent container.",
NO_COMPOSITION_WARNING =
"Could not add to composition; no composition in ";
/**
*
* @constructor
*/
function CreationService(persistenceService, uuidService, $q, $log) {
function doPersist(space, id, model) {
return persistenceService.createObject(
space,
id,
model
).then(function () { return id; });
}
function addToComposition(id, parent) {
var mutatationResult = parent.useCapability("mutation", function (model) {
if (Array.isArray(model.composition)) {
if (model.composition.indexOf(id) === -1) {
model.composition.push(id);
}
} else {
$log.warn(NO_COMPOSITION_WARNING + parent.getId());
}
});
return $q.when(mutatationResult).then(function (result) {
var persistence = parent.getCapability("persistence");
if (!result) {
$log.error("Could not mutate " + parent.getId());
}
if (!persistence) {
$log.error([
"Expected to be able to persist ",
parent.getId(),
" but could not."
].join(""));
return undefined;
}
return persistence.persist();
});
}
function createObject(model, parent) {
var persistence = parent.getCapability("persistence"),
result = $q.defer(),
space;
if (persistence) {
space = persistence.getSpace();
return $q.when(
uuidService.getUUID()
).then(function (id) {
return doPersist(space, id, model);
}).then(function (id) {
return addToComposition(id, parent);
});
} else {
$log.warn(NON_PERSISTENT_WARNING);
$q.reject(new Error(NON_PERSISTENT_WARNING));
}
return result.promise;
}
return {
createObject: createObject
};
}
return CreationService;
}
);

View File

@@ -0,0 +1,29 @@
/*global define,Promise*/
/**
* Module defining UUIDService. Created by vwoeltje on 11/12/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function UUIDService() {
var counter = Date.now();
return {
getUUID: function () {
counter += 1;
return counter.toString(36);
}
};
}
return UUIDService;
}
);

View File

@@ -0,0 +1,35 @@
/*global define,Promise*/
/**
* Module defining NavigateAction. Created by vwoeltje on 11/10/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function NavigateAction(navigationService, context) {
var domainObject = context.domainObject;
function perform() {
return Promise.resolve(
navigationService.setNavigation(domainObject)
);
}
return {
perform: perform
};
}
NavigateAction.appliesTo = function (context) {
return context.domainObject !== undefined;
};
return NavigateAction;
}
);

View File

@@ -0,0 +1,50 @@
/*global define,Promise*/
/**
* Module defining NavigationService. Created by vwoeltje on 11/10/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function NavigationService() {
var navigated,
callbacks = [];
function getNavigation() {
return navigated;
}
function setNavigation(value) {
navigated = value;
callbacks.forEach(function (callback) {
callback(value);
});
}
function addListener(callback) {
callbacks.push(callback);
}
function removeListener(callback) {
callbacks = callbacks.filter(function (cb) {
return cb !== callback;
});
}
return {
getNavigation: getNavigation,
setNavigation: setNavigation,
addListener: addListener,
removeListener: removeListener
};
}
return NavigationService;
}
);

View File

@@ -0,0 +1,41 @@
/*global define,screenfull,Promise*/
/**
* Module defining FullscreenAction. Created by vwoeltje on 11/18/14.
*/
define(
["../../lib/screenfull.min"],
function () {
"use strict";
var ENTER_FULLSCREEN = "Enter full screen mode.",
EXIT_FULLSCREEN = "Exit full screen mode.";
/**
*
* @constructor
*/
function FullscreenAction(context) {
return {
perform: function () {
screenfull.toggle();
},
getMetadata: function () {
// We override getMetadata, because the glyph and
// description need to be determined at run-time
// based on whether or not we are currently
// full screen.
var metadata = Object.create(FullscreenAction);
metadata.glyph = screenfull.isFullscreen ? "_" : "z";
metadata.description = screenfull.isFullscreen ?
EXIT_FULLSCREEN : ENTER_FULLSCREEN;
metadata.group = "windowing";
metadata.context = context;
return metadata;
}
};
}
return FullscreenAction;
}
);

View File

@@ -0,0 +1,25 @@
/*global define,Promise*/
/**
* Module defining NewWindowAction. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function NewWindowAction($window) {
return {
perform: function () {
$window.alert("Not yet functional. This will open objects in a new window.");
}
};
}
return NewWindowAction;
}
);