Admin Project Group UI

User interface for project group administration. Nothing fancy -
I replaced the Projects icon with one that had a plural version
(box, boxes). This patch depends on 106507.

Change-Id: Ie117bbdf35f385ba016cb5a818cf05cd12ad7361
This commit is contained in:
Michael Krotscheck 2014-07-07 14:55:46 -07:00
parent 329f878568
commit a0cc35c72f
14 changed files with 1148 additions and 31 deletions

@ -0,0 +1,89 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
/**
* Administration controller for project groups.
*/
angular.module('sb.admin').controller('ProjectGroupAdminController',
function ($scope, $modal, ProjectGroup) {
'use strict';
/**
* The project groups.
*
* @type {Array}
*/
$scope.projectGroups = [];
/**
* The search filter query string.
*
* @type {string}
*/
$scope.filterQuery = '';
/**
* Launches the add-project-group modal.
*/
$scope.addProjectGroup = function () {
$modal.open(
{
templateUrl: 'app/admin/template/project_group_new.html',
controller: 'ProjectGroupNewController'
}).result.then(function () {
// On success, reload the page.
$scope.search();
});
};
/**
* Open up the delete project group modal.
*
* @param projectGroup
*/
$scope.deleteProjectGroup = function (projectGroup) {
var modalInstance = $modal.open({
templateUrl: 'app/admin/template/project_group_delete.html',
controller: 'ProjectGroupDeleteController',
resolve: {
projectGroup: function () {
return projectGroup;
}
}
});
// Reset the view after successful completion.
modalInstance.result.then(
function () {
$scope.search();
}
);
};
/**
* Execute a search.
*/
$scope.search = function () {
var searchQuery = $scope.filterQuery || '';
$scope.projectGroups = ProjectGroup.query({
title: searchQuery
});
};
// Initialize
$scope.search();
});

@ -0,0 +1,44 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
/**
* Controller for the project group delete modal popup.
*/
angular.module('sb.profile').controller('ProjectGroupDeleteController',
function ($scope, projectGroup, $modalInstance) {
'use strict';
$scope.projectGroup = projectGroup;
// Set our progress flags and clear previous error conditions.
$scope.isUpdating = true;
$scope.error = {};
/**
*
*/
$scope.remove = function () {
$scope.projectGroup.$delete(
function () {
$modalInstance.close('success');
}
);
};
$scope.close = function () {
$modalInstance.dismiss('cancel');
};
});

@ -0,0 +1,203 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
/**
* New Project Group edit controller.
*/
angular.module('sb.profile').controller('ProjectGroupEditController',
function ($q, $log, $scope, $state, projectGroup, projects, Project,
ProjectGroupItem, ArrayUtil) {
'use strict';
/**
* The project group we're editing. Resolved by the route.
*/
$scope.projectGroup = projectGroup;
/**
* The list of projects in this group (Resolved by the route).
*/
$scope.projects = projects;
/**
* A collection of all project ID's that have been loaded on
* initialization. This list is used to determine the project member
* diff.
*/
var loadedIds = [];
$scope.projects.forEach(function (project) {
loadedIds.push(project.id);
});
/**
* UI flag, are we saving?
*
* @type {boolean}
*/
$scope.isSaving = false;
/**
* Project typeahead search method.
*/
$scope.searchProjects = function (value) {
var deferred = $q.defer();
Project.query({name: value, limit: 10},
function (results) {
// Dedupe the results.
var idxList = [];
for (var i = 0; i < $scope.projects.length; i++) {
var project = $scope.projects[i];
if (!!project) {
idxList.push(project.id);
}
}
for (var j = results.length - 1; j >= 0; j--) {
var resultId = results[j].id;
if (idxList.indexOf(resultId) > -1) {
results.splice(j, 1);
}
}
deferred.resolve(results);
},
function (error) {
$log.error(error);
deferred.resolve([]);
});
return deferred.promise;
};
/**
* Formats the project name.
*/
$scope.formatProjectName = function (model) {
if (!!model) {
return model.name;
}
return '';
};
/**
* Remove a project from the list
*/
$scope.removeProject = function (index) {
$scope.projects.splice(index, 1);
};
/**
* Save the project and the associated groups
*/
$scope.save = function () {
$scope.isSaving = true;
var promises = [];
// Get the desired ID's.
var desiredIds = [];
$scope.projects.forEach(function (project) {
desiredIds.push(project.id);
});
// Intersect loaded vs. current to get a list of project
// reference to delete.
var idsToDelete = ArrayUtil.difference(loadedIds, desiredIds);
idsToDelete.forEach(function (id) {
// Get a deferred promise...
var deferred = $q.defer();
// Construct the item.
var item = new ProjectGroupItem({
id: id,
projectGroupId: projectGroup.id
});
// Delete the item.
item.$delete(function (result) {
deferred.resolve(result);
},
function (error) {
deferred.reject(error);
}
);
promises.push(deferred.promise);
});
// Intersect current vs. loaded to get a list of project
// reference to delete.
var idsToAdd = ArrayUtil.difference(desiredIds, loadedIds);
idsToAdd.forEach(function (id) {
// Get a deferred promise...
var deferred = $q.defer();
// Construct the item.
var item = new ProjectGroupItem({
id: id,
projectGroupId: projectGroup.id
});
// Delete the item.
item.$create(function (result) {
deferred.resolve(result);
},
function (error) {
deferred.reject(error);
}
);
promises.push(deferred.promise);
});
// Save the project group itself.
var deferred = $q.defer();
promises.push(deferred.promise);
$scope.projectGroup.$update(function (success) {
deferred.resolve(success);
}, function (error) {
$log.error(error);
deferred.reject(error);
});
// Roll all the promises into one big happy promise.
$q.all(promises).then(
function () {
$state.go('admin.project_group', {});
},
function (error) {
$log.error(error);
}
);
};
/**
* Add project.
*/
$scope.addProject = function () {
$scope.projects.push({});
};
/**
* Insert item into the project list.
*/
$scope.selectNewProject = function (index, model) {
// Put our model into the array
$scope.projects[index] = model;
};
});

@ -0,0 +1,44 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
/**
* Controller for the project group member list.
*/
angular.module('sb.admin').controller('ProjectGroupItemController',
function ($scope, $log, ProjectGroupItem) {
'use strict';
$scope.projectGroupItems = [];
$scope.loadingProjectGroupItems = false;
if (!$scope.projectGroup) {
return;
}
var id = $scope.projectGroup.id;
$scope.loadingProjectGroupItems = true;
ProjectGroupItem.query({
projectGroupId: id
},
function (results) {
$scope.loadingProjectGroupItems = false;
$scope.projectGroupItems = results;
}, function (error) {
$log.error(error);
$scope.loadingProjectGroupItems = false;
});
});

@ -0,0 +1,169 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
/**
* New Project Group modal controller.
*/
angular.module('sb.profile').controller('ProjectGroupNewController',
function ($q, $log, $scope, $modalInstance, ProjectGroup, ProjectGroupItem,
Project) {
'use strict';
/**
* Flag for the UI to indicate that we're saving.
*
* @type {boolean}
*/
$scope.isSaving = false;
/**
* The list of projects.
*
* @type {{}[]}
*/
$scope.projects = [
{}
];
/**
* The new project group.
*
* @type {ProjectGroup}
*/
$scope.projectGroup = new ProjectGroup();
/**
* Project typeahead search method.
*/
$scope.searchProjects = function (value) {
var deferred = $q.defer();
Project.query({name: value, limit: 10},
function (results) {
// Dedupe the results.
var idxList = [];
for (var i = 0; i < $scope.projects.length; i++) {
var project = $scope.projects[i];
if (!!project) {
idxList.push(project.id);
}
}
for (var j = results.length - 1; j >= 0; j--) {
var resultId = results[j].id;
if (idxList.indexOf(resultId) > -1) {
results.splice(j, 1);
}
}
deferred.resolve(results);
},
function (error) {
$log.error(error);
deferred.resolve([]);
});
return deferred.promise;
};
/**
* Formats the project name.
*/
$scope.formatProjectName = function (model) {
if (!!model) {
return model.name;
}
return '';
};
/**
* Add project.
*/
$scope.addProject = function () {
$scope.projects.push({});
};
/**
* Insert item into the project list.
*/
$scope.selectNewProject = function (index, model) {
// Put our model into the array
$scope.projects[index] = model;
};
/**
* Remove a project from the list.
*/
$scope.removeProject = function (index) {
$scope.projects.splice(index, 1);
};
/**
* Saves the project group
*/
$scope.save = function () {
$scope.isSaving = true;
// Create a new project group
$scope.projectGroup.$save(function (projectGroup) {
$modalInstance.close(projectGroup);
var promises = [];
$scope.projects.forEach(
function (project) {
// Get a deferred promise...
var deferred = $q.defer();
// Construct the item.
var item = new ProjectGroupItem({
id: project.id,
projectGroupId: projectGroup.id
});
// Create the item.
item.$create(function (result) {
deferred.resolve(result);
},
function (error) {
deferred.reject(error);
}
);
promises.push(deferred.promise);
}
);
// Wait for all the promises to finish.
$q.all(promises, function () {
$modalInstance.close(projectGroup);
}, function (error) {
$log.error(error);
$modalInstance.dismiss(error);
});
}, function (error) {
$scope.isSaving = false;
$log.error(error);
});
};
/**
* Close this modal without saving.
*/
$scope.close = function () {
$modalInstance.dismiss('cancel');
};
});

@ -17,12 +17,13 @@
/**
* The StoryBoard administration module.
*/
angular.module('sb.admin', [ 'sb.services', 'sb.templates', 'ui.router'])
angular.module('sb.admin', [ 'sb.services', 'sb.templates', 'sb.util',
'ui.router'])
.config(function ($stateProvider, $urlRouterProvider, PermissionResolver) {
'use strict';
// Routing Defaults.
$urlRouterProvider.when('/admin', '/admin/index');
$urlRouterProvider.when('/admin', '/admin/project_group');
// Declare the states for this module.
$stateProvider
@ -35,8 +36,24 @@ angular.module('sb.admin', [ 'sb.services', 'sb.templates', 'ui.router'])
.requirePermission('is_superuser', true)
}
})
.state('admin.index', {
url: '/index',
templateUrl: 'app/admin/template/index.html'
.state('admin.project_group', {
url: '/project_group',
templateUrl: 'app/admin/template/project_group.html',
controller: 'ProjectGroupAdminController'
})
.state('admin.project_group_edit', {
url: '/project_group/:id',
templateUrl: 'app/admin/template/project_group_edit.html',
controller: 'ProjectGroupEditController',
resolve: {
projectGroup: function ($stateParams, ProjectGroup) {
return ProjectGroup.get({id: $stateParams.id}).$promise;
},
projects: function ($stateParams, ProjectGroupItem) {
return ProjectGroupItem.query(
{projectGroupId: $stateParams.id}
).$promise;
}
}
});
});

@ -1,25 +0,0 @@
<!--
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
~
~ 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.
-->
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>StoryBoard Administration</h1>
<p>No admin functions are defined yet. </p>
</div>
</div>
</div>

@ -0,0 +1,144 @@
<!--
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
~
~ 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.
-->
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1><i class="fa fa-sb-project-group"></i> Project Groups</h1>
</div>
</div>
<div class="row">
<div class="col-md-3 col-sm-4 col-xs-10">
<div class="has-feedback has-feedback-no-label">
<input id="projectInput"
type="text"
class="form-control"
ng-model="filterQuery"
required
ng-disabled="!projectGroups.$resolved"
ng-enter="search()"
placeholder="Search Project Groups">
<span class="form-control-feedback text-muted
form-control-feedback-sm">
<i class="fa fa-refresh fa-spin"
ng-hide="projectGroups.$resolved"></i>
<i class="fa fa-search"
ng-show="projectGroups.$resolved"></i>
</span>
</div>
</div>
<div class="col-md-6 col-sm-5 hidden-xs">
<div class="form form-horizontal">
<p class="form-control-static text-muted">
({{projectGroups.length}} found)
</p>
</div>
</div>
<div class="col-sm-3 col-xs-2">
<button class="btn btn-primary pull-right"
ng-click="addProjectGroup()">
<i class="fa fa-plus"></i>
<span class="hidden-xs">Add Project Group</span>
</button>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<!-- Spacer -->
<br/>
<table class="table table-condensed table-striped">
<tbody ng-if="!projectGroups.$resolved">
<tr>
<td>
<span class="text-muted text-center">
<i class="fa fa-spin fa-refresh"></i>
</span>
</td>
</tr>
</tbody>
<tbody ng-if="projectGroups.$resolved && projectGroups.length == 0">
<tr>
<td class="text-center">
<em class="text-muted">
No project groups found.
</em>
</td>
</tr>
</tbody>
<tbody ng-if="projectGroups.$resolved && projectGroups.length > 0">
<tr ng-repeat="projectGroup in projectGroups">
<td class="col-sm-3 col-xs-4">
<i class="fa fa-sb-project-group text-muted"></i>
<a href="#!/admin/project_group/{{projectGroup.id}}">
{{projectGroup.title}}
</a>
</td>
<td class="col-sm-9 col-xs-8"
ng-controller="ProjectGroupItemController">
<div class="pull-right btn-group btn-group-xs">
<a class="btn btn-link "
href="#!/admin/project_group/{{projectGroup.id}}">
<i class="fa fa-edit fa-lg"></i>
</a>
<a class="btn btn-link "
href=""
ng-click="deleteProjectGroup(projectGroup)">
<i class="fa fa-times fa-lg"></i>
</a>
</div>
<table>
<tbody ng-if="loadingProjectGroupItems">
<tr>
<td>
<span class="text-muted text-center">
<i class="fa fa-spin fa-refresh"></i>
</span>
</td>
</tr>
</tbody>
<tbody ng-if="projectGroupItems.length == 0 && !loadingProjectGroupItems">
<tr>
<td>
<em class="text-muted">
This group does not contain any
projects.
</em>
</td>
</tr>
</tbody>
<tbody ng-if="projectGroupItems.length > 0">
<tr ng-repeat="project in projectGroupItems">
<td>
<a href="#!/project/{{project.id}}">
<i class="fa fa-sb-project text-muted"></i>
{{project.name}}
</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

@ -0,0 +1,41 @@
<!--
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
~
~ 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.
-->
<div class="panel panel-default">
<div class="panel-heading">
<button type="button" class="close" aria-hidden="true"
ng-click="close()">&times;</button>
<h3 class="panel-title">{{projectGroup.title}}</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<h2 class="text-danger text-center">
Are you certain that you want to delete this project group?
</h2>
<p class="text-center lead">
This action cannot be undone.
</p>
<div class="text-center">
<a href="" class="btn btn-danger" ng-click="remove()">
Remove this project group
</a>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,140 @@
<!--
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
~
~ 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.
-->
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1><i class="fa fa-sb-project-group"></i> {{projectGroup.title}}
</h1>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<form class="form-horizontal"
role="form"
name="projectGroupForm">
<div class="form-group">
<label for="title" class="col-sm-2 control-label">
Title:
</label>
<div class="col-sm-10">
<input id="title"
type="text"
class="form-control"
ng-model="projectGroup.title"
required
ng-disabled="isSaving"
placeholder="Project Group Title">
</div>
</div>
<div class="form-group">
<label for="name" class="col-sm-2 control-label">
URL:
</label>
<div class="col-sm-10">
<input id="name"
type="text"
class="form-control"
ng-model="projectGroup.name"
required
ng-disabled="isSaving"
placeholder="URL Stub for the project group">
</div>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-sm-2 text-right">
<label class="control-label">
Projects:
</label>
</div>
<div class="col-sm-10">
<form role="form" name="projectsForm">
<table class="table table-striped table-outlined">
<tbody>
<tr ng-repeat="(index, project) in projects"
ng-include
src="'/inline/project_row.html'">
</tr>
</tbody>
</table>
</form>
</div>
</div>
<div class="row">
<div class="col-xs-4 col-xs-offset-2">
<button type="button"
class="btn btn-default"
ng-disabled="isSaving"
ng-click="addProject()">
&plus;
Add another project
</button>
</div>
<div class="col-xs-6 text-right">
<button type="button"
class="btn btn-primary"
ng-click="save()"
ng-disabled="!projectGroupForm.$valid || !projectsForm.$valid || isSaving">
Save
</button>
<a href="#!/admin/project_group"
ng-disabled="isSaving"
class="btn btn-default">
Cancel
</a>
</div>
</div>
</div>
<!-- Template for story metadata -->
<script type="text/ng-template" id="/inline/project_row.html">
<td class="col-xs-11">
<div class="has-feedback has-feedback-no-label">
<input id="project"
type="text"
placeholder="Select a Project"
required
ng-model="project"
typeahead-editable="false"
typeahead="project as project.name for project
in searchProjects($viewValue)"
typeahead-loading="loadingProjects"
typeahead-on-select="selectNewProject(index, $model)"
typeahead-input-formatter="formatProjectName($model)"
ng-disabled="isSaving"
class="form-control input-sm"
/>
<span class="form-control-feedback text-muted
form-control-feedback-sm">
<i class="fa fa-refresh fa-spin" ng-show="loadingProjects"></i>
<i class="fa fa-search" ng-hide="loadingProjects"></i>
</span>
</div>
</td>
<th class="col-xs-1"
ng-show="projects.length > 1">
<button type="button" class="close"
ng-click="removeProject(index)">
&times;
</button>
</th>
</script>

@ -0,0 +1,136 @@
<!--
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
~
~ 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.
-->
<div class="panel panel-default">
<div class="panel-heading">
<button type="button" class="close" aria-hidden="true"
ng-click="close()">&times;</button>
<h3 class="panel-title">New Project Group</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-xs-12">
<form class="form-horizontal"
role="form"
name="projectGroupForm">
<div class="form-group">
<label for="title" class="col-sm-2 control-label">
Title:
</label>
<div class="col-sm-10">
<input id="title"
type="text"
class="form-control"
ng-model="projectGroup.title"
required
ng-disabled="isSaving"
placeholder="Project Group Title">
</div>
</div>
<div class="form-group">
<label for="name" class="col-sm-2 control-label">
URL:
</label>
<div class="col-sm-10">
<input id="name"
type="text"
class="form-control"
ng-model="projectGroup.name"
required
ng-disabled="isSaving"
placeholder="URL Stub for the project group">
</div>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<form role="form" name="projectsForm">
<table class="table table-striped table-outlined">
<tbody>
<tr ng-repeat="(index, project) in projects"
ng-include
src="'/inline/project_row.html'">
</tr>
</tbody>
</table>
</form>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<button type="button"
class="btn btn-default"
ng-disabled="isSaving"
ng-click="addProject()">
&plus;
Add another project
</button>
</div>
<div class="col-xs-6 text-right">
<button type="button"
class="btn btn-primary"
ng-click="save()"
ng-disabled="!projectGroupForm.$valid || !projectsForm.$valid || isSaving">
Save Changes
</button>
<button type="button"
ng-click="close()"
ng-disabled="isSaving"
class="btn btn-default">
Cancel
</button>
</div>
</div>
</div>
</div>
<!-- Template for story metadata -->
<script type="text/ng-template" id="/inline/project_row.html">
<td class="col-xs-11">
<div class="has-feedback has-feedback-no-label">
<input id="project"
type="text"
placeholder="Select a Project"
required
ng-model="project"
typeahead-editable="false"
typeahead="project as project.name for project
in searchProjects($viewValue)"
typeahead-loading="loadingProjects"
typeahead-on-select="selectNewProject(index, $model)"
typeahead-input-formatter="formatProjectName($model)"
ng-disabled="isSaving"
class="form-control input-sm"
/>
<span class="form-control-feedback text-muted
form-control-feedback-sm">
<i class="fa fa-refresh fa-spin" ng-show="loadingProjects"></i>
<i class="fa fa-search" ng-hide="loadingProjects"></i>
</span>
</div>
</td>
<th class="col-xs-1"
ng-show="projects.length > 1">
<button type="button" class="close"
ng-click="removeProject(index)">
&times;
</button>
</th>
</script>

@ -0,0 +1,50 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
/**
* The angular resource abstraction that allows us to access children of
* project groups.
*
* @author Michael Krotscheck
*/
angular.module('sb.services').factory('ProjectGroupItem',
function ($resource, storyboardApiBase) {
'use strict';
return $resource(storyboardApiBase +
'/project_groups/:projectGroupId/projects/:id',
{
projectGroupId: '@projectGroupId',
id: '@id'
},
{
'create': {
method: 'PUT',
transformRequest: function () {
// The API endpoint takes no payload.
return '';
}
},
'delete': {
method: 'DELETE'
},
'query': {
method: 'GET',
isArray: true,
responseType: 'json'
}
}
);
});

@ -0,0 +1,64 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
/**
* Array utilities.
*/
angular.module('sb.util').factory('ArrayUtil',
function () {
'use strict';
return {
/**
* Performs a logical intersection on two arrays. Given A, and B,
* returns AB, the set of all objects that are in both A and B.
*
* @param A
* @param B
*/
intersection: function (A, B) {
var result = [];
A.forEach(function (item) {
if (B.indexOf(item) > -1) {
result.push(item);
}
});
return result;
},
/**
* Performs a logical difference operation on the two
* arrays. Given sets U and A it will return U\A, the set of all
* members of U that are not members of A.
*
* @param U
* @param A
*/
difference: function (U, A) {
var result = [];
U.forEach(function (item) {
if (A.indexOf(item) === -1) {
result.push(item);
}
});
return result;
}
};
}
);

@ -23,4 +23,5 @@
.@{fa-css-prefix}-sb-user:before { content: @fa-var-user; }
.@{fa-css-prefix}-sb-task:before { content: @fa-var-tag; }
.@{fa-css-prefix}-sb-story:before { content: @fa-var-list-ul; }
.@{fa-css-prefix}-sb-project:before { content: @fa-var-flag; }
.@{fa-css-prefix}-sb-project:before { content: @fa-var-cube; }
.@{fa-css-prefix}-sb-project-group:before { content: @fa-var-cubes; }