MVP Storyboard Client

This patch constitutes the work discussed in Brussels for the MVP webclient.
 Changes made, as follows:

1- Updated header to contain Overview, Projects, Stories, and a "New Story"
button, including mobile treatment.
2- Updated navigation header to use selection styling for mobile and regular.
3- Application index contains new-story button.
4- Main application container now has a minheight.
5- Removed old controllers (code cleanup) before reimplementation.
6- Wired project views (require API support).
7- Wired story views (require API support).
8- Wired new story modal (require API support).
9- New task form (requires API support).
10- Wired up new functional tests and fixed a few issues in existing unit
tests.
11- Switched LESS compiler from recess to less, since recess can no longer
compile the most recent version of bootstrap.

Change-Id: Iddd5b29dd899d92f05ad7a9a63814d8599e167d4
This commit is contained in:
Michael Krotscheck 2014-02-03 18:09:58 +01:00
parent e31dce1163
commit 062836f2dd
51 changed files with 1533 additions and 1499 deletions

View File

@ -101,7 +101,6 @@ module.exports = function (grunt) {
concat: {
dist: {
src: [
dir.source + '/app/storyboard.js',
dir.source + '/app/**/module.js',
dir.source + '/app/**/*.js'
],
@ -119,19 +118,20 @@ module.exports = function (grunt) {
* Note: We're using LessCSS here because SASS requires ruby-compass,
* and cannot be easily installed with npm.
*/
recess: {
less: {
options: {
includePath: [
dir.bower + '/bootstrap/less/',
paths: [
dir.bower + '/bootstrap/less',
dir.bower + '/font-awesome/less/'
],
compile: true
cleancss: true,
strictMath: true,
strictUnits: true
},
theme: {
src: [
dir.source + '/styles/main.less'
],
dest: dir.output + '/styles/main.css'
files: {
'dist/styles/main.css': dir.source + '/styles/main.less'
}
}
},
@ -346,17 +346,16 @@ module.exports = function (grunt) {
watch: {
concat: {
files: [
dir.source + '/app/storyboard.js',
dir.source + '/app/**/module.js',
dir.source + '/app/**/*.js'
],
tasks: ['concat']
},
recess: {
less: {
files: [
dir.source + '/styles/**/*.less'
],
tasks: ['recess:theme']
tasks: ['less:theme']
},
copy: {
files: [
@ -505,7 +504,7 @@ module.exports = function (grunt) {
'jshint',
'useminPrepare',
'concat',
'recess',
'less',
'imagemin',
'html2js',
'copy',

View File

@ -1,22 +1,22 @@
{
"name": "storyboard-webclient",
"version": "0.0.1",
"dependencies": {
"jquery": "2.0.3",
"font-awesome": "4.0",
"angular": "1.2.9",
"angular-resource": "1.2.9",
"angular-cookies": "1.2.9",
"angular-sanitize": "1.2.9",
"bootstrap": "3.0.0",
"angular-ui-router": "0.2.7",
"angular-translate": "1.1.1"
},
"devDependencies": {
"angular-mocks": "1.2.9",
"angular-scenario": "1.2.9"
},
"resolutions": {
"angular": "1.2.9"
}
"name": "storyboard-webclient",
"version": "0.0.1",
"dependencies": {
"jquery": "2.0.3",
"font-awesome": "4.0",
"angular": "1.2.13",
"angular-resource": "1.2.13",
"angular-cookies": "1.2.13",
"angular-sanitize": "1.2.13",
"bootstrap": "3.1.0",
"angular-ui-router": "0.2.8-bowratic-tedium",
"angular-bootstrap": "0.10.0"
},
"devDependencies": {
"angular-mocks": "1.2.13",
"angular-scenario": "1.2.13"
},
"resolutions": {
"angular": "1.2.13"
}
}

View File

@ -26,13 +26,13 @@
"grunt-usemin": "2.0.2",
"grunt-contrib-htmlmin": "0.1.3",
"grunt-contrib-cssmin": "0.7.0",
"grunt-contrib-less": "0.9.0",
"grunt-karma": "0.6.2",
"grunt-contrib-connect": "0.5.0",
"grunt-contrib-watch": "0.5.3",
"grunt-contrib-jshint": "0.7.2",
"grunt-contrib-uglify": "0.2.7",
"grunt-contrib-imagemin": "0.4.0",
"grunt-recess": "0.5.0",
"grunt": "0.4.2",
"grunt-cli": "0.1.11",
"matchdep": "0.1.2",
@ -49,10 +49,10 @@
"grunt-shell": "0.6.1",
"karma-coverage": "0.1.4",
"grunt-env": "0.4.1",
"protractor": "0.15.0",
"grunt-protractor-runner": "0.2.0",
"protractor": "0.19.0",
"grunt-protractor-runner": "0.2.3",
"selenium-standalone": "2.39.0-2.7.0",
"karma-html-reporter": "~0.1.1",
"grunt-connect-proxy": "~0.1.7"
"karma-html-reporter": "0.1.1",
"grunt-connect-proxy": "0.1.8"
}
}

View File

@ -1,54 +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.
*/
/**
* View controller for the new project group form. Includes an intermediary
* 'saving' flag as well as room for an error response (though until we get
* a real API that'll be a bit tricky to test).
*/
angular.module('sb.project_groups').controller('ProjectGroupNewController',
function ($scope, $state, ProjectGroup) {
'use strict';
// View parameters.
$scope.newProjectGroup = new ProjectGroup();
$scope.isCreating = false;
$scope.error = {};
/**
* Submits the newly created project. If an error response is received,
* assigns it to the view and unsets various flags. The template
* should know how to handle it.
*/
$scope.createProjectGroup = function () {
// Clear everything and set the progress flag...
$scope.isCreating = true;
$scope.error = {};
$scope.newProjectGroup.$create(
function () {
// Success!
$state.go('project_groups.list');
},
function (error) {
// Error received. Ho hum.
$scope.isCreating = false;
$scope.error = error;
}
);
};
});

View File

@ -1,55 +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.
*/
/**
* The Storyboard project_group submodule handles most activity surrounding the
* creation and management of project_groups.
*/
angular.module('sb.project_groups', ['ui.router', 'sb.services', 'sb.util'])
.config(function ($stateProvider, $urlRouterProvider) {
'use strict';
// URL Defaults.
$urlRouterProvider.when('/project_groups', '/project_groups/list');
// Set our page routes.
$stateProvider
.state('project_groups', {
abstract: true,
url: '/project_groups',
template: '<div ui-view></div>'
})
.state('project_groups.list', {
url: '/list',
templateUrl: 'app/templates/project_groups/list.html',
controller: 'ProjectGroupListController'
})
.state('project_groups.edit', {
url: '/{id:[0-9]+}/edit',
templateUrl: 'app/templates/project_groups/edit.html',
controller: 'ProjectGroupDetailController'
})
.state('project_groups.detail', {
url: '/{id:[0-9]+}',
templateUrl: 'app/templates/project_groups/detail.html',
controller: 'ProjectGroupDetailController'
})
.state('project_groups.new', {
url: '/new',
templateUrl: 'app/templates/project_groups/new.html',
controller: 'ProjectGroupNewController'
});
});

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
* 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
* 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
@ -28,7 +28,7 @@
* seconds. 3 is preferable.
*/
angular.module('sb.projects').controller('ProjectDetailController',
function ($scope, $state, $stateParams, Project) {
function ($scope, $state, $stateParams, Project, Story) {
'use strict';
// Parse the ID
@ -43,6 +43,15 @@ angular.module('sb.projects').controller('ProjectDetailController',
*/
$scope.project = {};
/**
* The count of stories for this project.
*
* TODO(krotscheck): Once we have proper paging requests working,
* this should become a count-only request, so we can delegate project
* story searches to the ProjectStoryListController.
*/
$scope.projectStoryCount = 0;
/**
* UI flag for when we're initially loading the view.
*
@ -98,6 +107,15 @@ angular.module('sb.projects').controller('ProjectDetailController',
},
handleServiceError
);
// Load the count of stories while we're at it...
Story.query({project: id},
function (result, headers) {
// Only extract the total header...
$scope.projectStoryCount =
headers('X-List-Total') || result.length;
},
handleServiceError
);
}
/**
@ -118,25 +136,4 @@ angular.module('sb.projects').controller('ProjectDetailController',
handleServiceError
);
};
/**
* Scope method, invoke this when you'd like to delete this project.
*/
$scope.remove = function () {
// Set our progress flags and clear previous error conditions.
$scope.isUpdating = true;
$scope.error = {};
// Try to delete.
$scope.project.$delete(
function () {
// The deletion was successful, so head back to the list
// view.
$scope.isUpdating = false;
$state.go('project.list');
},
handleServiceError
);
};
});

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
* 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
* 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
@ -24,7 +24,15 @@ angular.module('sb.projects').controller('ProjectListController',
'use strict';
// Variables and methods available to the template...
$scope.projects = [];
function resetScope() {
$scope.projectCount = 0;
$scope.projectOffset = 0;
$scope.projectLimit = 10;
$scope.projects = [];
$scope.error = {};
}
$scope.searchQuery = '';
$scope.isSearching = false;
@ -33,18 +41,25 @@ angular.module('sb.projects').controller('ProjectListController',
*/
$scope.search = function () {
// Clear the scope and set the progress flag.
$scope.error = {};
resetScope();
$scope.isSearching = true;
$scope.projects = [];
// Execute the project query.
Project.search(
// Enable this once the API's there, mocks don't support
// searches yet
{/* q: $scope.searchQuery || '' */},
function (result) {
Project.query(
// Enable this once the API accepts search queries.
{ /*q: $scope.searchQuery || ''*/},
function (result, headers) {
// Extract metadata from returned headers.
var projectCount = headers('X-List-Total') || result.length;
var projectOffset = headers('X-List-Offset') || 0;
var projectLimit = headers('X-List-Limit') || result.length;
// Successful search results, apply the results to the
// scope and unset our progress flag.
$scope.projectCount = projectCount;
$scope.projectOffset = projectOffset;
$scope.projectLimit = projectLimit;
$scope.projects = result;
$scope.isSearching = false;
},
@ -58,5 +73,6 @@ angular.module('sb.projects').controller('ProjectListController',
};
// Initialize the view with a default search.
resetScope();
$scope.search();
});

View File

@ -15,16 +15,33 @@
*/
/**
* The projectGroup list controller handles discovery for all projectGroups,
* including search. Note that it is assumed that we implemented a search
* (inclusive), rather than a browse (exclusive) approach.
* This controller manages stories within the scope of a particular project.
*/
angular.module('sb.project_groups').controller('ProjectGroupListController',
function ($scope, ProjectGroup) {
angular.module('sb.projects').controller('ProjectStoryListController',
function ($scope, $state, $stateParams, Story, NewStoryService) {
'use strict';
// Parse the ID. Since we're in a nested state, we don't really need
// to sanity check here, but in case of a future refactor we'll
// go ahead and do so anyway.
var id = $stateParams.hasOwnProperty('id') ?
parseInt($stateParams.id, 10) :
null;
if (id === null) {
$state.go('index');
return;
}
// Variables and methods available to the template...
$scope.projectGroups = [];
function resetScope() {
$scope.storyCount = 0;
$scope.storyOffset = 0;
$scope.storyLimit = 10;
$scope.stories = [];
$scope.error = {};
}
$scope.searchQuery = '';
$scope.isSearching = false;
@ -33,19 +50,25 @@ angular.module('sb.project_groups').controller('ProjectGroupListController',
*/
$scope.search = function () {
// Clear the scope and set the progress flag.
$scope.error = {};
resetScope();
$scope.isSearching = true;
$scope.projectGroups = [];
// Execute the projectGroup query.
ProjectGroup.search(
// Enable this once the API's there, mocks don't support
// searches yet
{/* q: $scope.searchQuery || '' */},
function (result) {
// Execute the story query.
Story.query(
{project: id},
function (result, headers) {
// Extract metadata from returned headers.
var storyCount = headers('X-List-Total') || result.length;
var storyOffset = headers('X-List-Offset') || 0;
var storyLimit = headers('X-List-Limit') || result.length;
// Successful search results, apply the results to the
// scope and unset our progress flag.
$scope.projectGroups = result;
$scope.storyCount = storyCount;
$scope.storyOffset = storyOffset;
$scope.storyLimit = storyLimit;
$scope.stories = result;
$scope.isSearching = false;
},
function (error) {
@ -57,6 +80,11 @@ angular.module('sb.project_groups').controller('ProjectGroupListController',
);
};
$scope.newStory = function () {
NewStoryService.showNewStoryModal(id);
};
// Initialize the view with a default search.
resetScope();
$scope.search();
});

View File

@ -22,8 +22,12 @@ angular.module('sb.projects', ['ui.router', 'sb.services', 'sb.util'])
.config(function ($stateProvider, $urlRouterProvider) {
'use strict';
// URL Defaults.
// Routing Defaults.
$urlRouterProvider.when('/project', '/project/list');
$urlRouterProvider.when('/project/{id:[0-9]+}',
function ($match) {
return '/project/' + $match.id + '/overview';
});
// Set our page routes.
$stateProvider
@ -37,16 +41,29 @@ angular.module('sb.projects', ['ui.router', 'sb.services', 'sb.util'])
templateUrl: 'app/templates/project/list.html',
controller: 'ProjectListController'
})
.state('project.edit', {
url: '/{id:[0-9]+}/edit',
templateUrl: 'app/templates/project/edit.html',
controller: 'ProjectDetailController'
})
.state('project.detail', {
abstract: true,
url: '/{id:[0-9]+}',
templateUrl: 'app/templates/project/detail.html',
controller: 'ProjectDetailController'
})
.state('project.detail.overview', {
url: '/overview',
templateUrl: 'app/templates/project/overview.html'
})
.state('project.detail.edit', {
url: '/edit',
templateUrl: 'app/templates/project/edit.html'
})
.state('project.detail.delete', {
url: '/delete',
templateUrl: 'app/templates/project/delete.html'
})
.state('project.detail.stories', {
url: '/stories',
templateUrl: 'app/templates/project/stories.html',
controller: 'ProjectStoryListController'
})
.state('project.new', {
url: '/new',
templateUrl: 'app/templates/project/new.html',

View File

@ -38,9 +38,10 @@ angular.module('sb.services')
'delete': {
method: 'DELETE'
},
'search': {
'query': {
method: 'GET',
isArray: true
isArray: true,
responseType: 'json'
}
};
}

View File

@ -0,0 +1,29 @@
/*
* 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 stories.
*
* @see storyboardApiSignature
*/
angular.module('sb.services').factory('Story',
function ($resource, storyboardApiBase, storyboardApiSignature) {
'use strict';
return $resource(storyboardApiBase + '/stories/:id',
{id: '@id'},
storyboardApiSignature);
});

View File

@ -13,36 +13,25 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Project group detail & manipulation controller. Usable for any view that
* wants to view, edit, or delete a project group, though views don't have to
* use all the functions therein. Includes flags for busy time, error responses
* and more.
*
* This controller assumes that the $stateParams object is both injectable and
* contains an ":id" property that indicates which project should be loaded. At
* the moment it will only set a 'isLoading' flag to indicate that data is
* loading. If loading the data is anticipated to take longer than 3 seconds,
* this will need to be updated to display a sane progress.
*
* Do not allow loading of this (or any) controller to take longer than 10
* seconds. 3 is preferable.
* Story detail & manipulation controller.
*/
angular.module('sb.project_groups').controller('ProjectGroupDetailController',
function ($scope, $state, $stateParams, ProjectGroup) {
angular.module('sb.story').controller('StoryDetailController',
function ($scope, $state, $stateParams, Story, Task) {
'use strict';
// Parse the ID
var id = $stateParams.hasOwnProperty('id') ?
parseInt($stateParams.id, 10) :
var id = $stateParams.hasOwnProperty('storyId') ?
parseInt($stateParams.storyId, 10) :
null;
/**
* The project group we're manipulating right now.
*
* @type ProjectGroup
* The story we're manipulating right now.
*/
$scope.projectGroup = {};
$scope.story = {};
$scope.tasks = [];
$scope.newTask = new Task();
/**
* UI flag for when we're initially loading the view.
@ -76,6 +65,20 @@ angular.module('sb.project_groups').controller('ProjectGroupDetailController',
$scope.isUpdating = false;
}
/**
* Loads the tasks for this story
*/
function loadTasks() {
$scope.tasks = [];
Task.query(
{story: id},
function (result) {
$scope.tasks = result;
},
handleServiceError
);
}
// Sanity check, do we actually have an ID? (zero is falsy)
if (!id && id !== 0) {
@ -89,18 +92,32 @@ angular.module('sb.project_groups').controller('ProjectGroupDetailController',
$scope.isLoading = false;
} else {
// We've got an ID, so let's load it...
ProjectGroup.read(
Story.read(
{'id': id},
function (result) {
// We've got a result, assign it to the view and unset our
// loading flag.
$scope.projectGroup = result;
$scope.story = result;
$scope.isLoading = false;
},
handleServiceError
);
loadTasks();
}
/**
* Adds a task.
*/
$scope.addTask = function () {
$scope.newTask.story_id = id;
$scope.newTask.$save(function () {
loadTasks();
$scope.newTask = new Task();
});
};
/**
* Scope method, invoke this when you want to update the project.
*/
@ -110,34 +127,11 @@ angular.module('sb.project_groups').controller('ProjectGroupDetailController',
$scope.error = {};
// Invoke the save method and wait for results.
$scope.projectGroup.$update(
function () {
$scope.story.$update(
function (result) {
// Unset our loading flag and navigate to the detail view.
$scope.isUpdating = false;
$state.go('project_groups.detail', {
id: $scope.projectGroup.id
});
},
handleServiceError
);
};
/**
* Scope method, invoke this when you'd like to delete this project.
*/
$scope.remove = function () {
// Set our progress flags and clear previous error conditions.
$scope.isUpdating = true;
$scope.error = {};
// Try to delete.
$scope.projectGroup.$delete(
function () {
// The deletion was successful, so head back to the list
// view.
$scope.isUpdating = false;
$state.go('project_groups.list');
$state.go('story.detail.overview', {storyId: result.id});
},
handleServiceError
);

View File

@ -15,16 +15,26 @@
*/
/**
* The team list controller handles discovery for all teams, including
* search. Note that it is assumed that we implemented a search (inclusive),
* rather than a browse (exclusive) approach.
* Controller for our story list.
*/
angular.module('sb.teams').controller('TeamsListController',
function ($scope, Team) {
angular.module('sb.story').controller('StoryListController',
function ($scope, $modal, NewStoryService, Story) {
'use strict';
$scope.newStory = function () {
NewStoryService.showNewStoryModal();
};
// Variables and methods available to the template...
$scope.teams = [];
function resetScope() {
$scope.storyCount = 0;
$scope.storyOffset = 0;
$scope.storyLimit = 10;
$scope.stories = [];
$scope.error = {};
}
$scope.searchQuery = '';
$scope.isSearching = false;
@ -33,19 +43,26 @@ angular.module('sb.teams').controller('TeamsListController',
*/
$scope.search = function () {
// Clear the scope and set the progress flag.
$scope.error = {};
resetScope();
$scope.isSearching = true;
$scope.teams = [];
// Execute the team search.
Team.search(
// Enable this once the API's there, mocks don't support
// searches yet
{/* q: $scope.searchQuery || '' */},
function (result) {
// Execute the story query.
Story.query(
// Enable this once the API accepts search queries.
{},
function (result, headers) {
// Extract metadata from returned headers.
var storyCount = headers('X-List-Total') || result.length;
var storyOffset = headers('X-List-Offset') || 0;
var storyLimit = headers('X-List-Limit') || result.length;
// Successful search results, apply the results to the
// scope and unset our progress flag.
$scope.teams = result;
$scope.storyCount = storyCount;
$scope.storyOffset = storyOffset;
$scope.storyLimit = storyLimit;
$scope.stories = result;
$scope.isSearching = false;
},
function (error) {
@ -58,5 +75,6 @@ angular.module('sb.teams').controller('TeamsListController',
};
// Initialize the view with a default search.
resetScope();
$scope.search();
});

View File

@ -0,0 +1,47 @@
/*
* 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 "new story" modal popup.
*/
angular.module('sb.story').controller('StoryModalController',
function ($scope, $modalInstance, params, Project, Story) {
'use strict';
$scope.story = new Story();
$scope.projects = Project.query({},
function (results) {
if (params.projectId !== null) {
for (var i = 0; i < results.length; i++) {
var project = results[i];
if (project.id === params.projectId) {
$scope.project = project;
return;
}
}
}
});
$scope.save = function () {
$scope.story.$create(function () {
$modalInstance.dismiss('success');
});
};
$scope.close = function () {
$modalInstance.dismiss('cancel');
};
});

71
src/app/stories/module.js Normal file
View File

@ -0,0 +1,71 @@
/*
* 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 Storyboard story submodule handles most activity surrounding the
* creation and management of stories, their tasks, and comments.
*/
angular.module('sb.story', ['ui.router', 'sb.services', 'sb.util'])
.config(function ($stateProvider, $urlRouterProvider) {
'use strict';
// URL Defaults.
$urlRouterProvider.when('/story', '/story/list');
$urlRouterProvider.when('/story/{id:[0-9]+}',
function ($match) {
return '/story/' + $match.id + '/overview';
});
$urlRouterProvider.when('/story/{storyId:[0-9]+}/task',
function ($match) {
return '/story/' + $match.storyId + '/overview';
});
$urlRouterProvider.when('/story/{storyId:[0-9]+}/task/{taskId:[0-9]+}',
function ($match) {
return '/story/' + $match.storyId +
'/task/' + $match.taskId;
});
// Set our page routes.
$stateProvider
.state('story', {
abstract: true,
url: '/story',
template: '<div ui-view></div>'
})
.state('story.list', {
url: '/list',
templateUrl: 'app/templates/story/list.html',
controller: 'StoryListController'
})
.state('story.detail', {
url: '/{storyId:[0-9]+}',
abstract: true,
templateUrl: 'app/templates/story/detail.html',
controller: 'StoryDetailController'
})
.state('story.detail.overview', {
url: '/overview',
templateUrl: 'app/templates/story/overview.html'
})
.state('story.detail.edit', {
url: '/edit',
templateUrl: 'app/templates/story/edit.html'
})
.state('story.detail.delete', {
url: '/delete',
templateUrl: 'app/templates/story/delete.html'
});
});

View File

@ -0,0 +1,47 @@
/*
* 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.
*/
angular.module('sb.story')
.factory('NewStoryService', function ($modal, $log) {
'use strict';
return {
showNewStoryModal: function (projectId) {
var modalInstance = $modal.open(
{
templateUrl: 'app/templates/story/new.html',
controller: 'StoryModalController',
resolve: {
params: function () {
return {
projectId: projectId || null
};
}
}
}
);
modalInstance.result.then(function () {
// Do nothing.
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
}
};
}
);

View File

@ -0,0 +1,27 @@
/*
* 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 our application header.
*/
angular.module('storyboard').controller('HeaderController',
function ($scope, $modal, NewStoryService) {
'use strict';
$scope.newStory = function () {
NewStoryService.showNewStoryModal();
};
});

View File

@ -0,0 +1,27 @@
/*
* 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 our home(index) page, currently just a placeholder.
*/
angular.module('storyboard').controller('HomeController',
function ($scope, $modal, NewStoryService) {
'use strict';
$scope.newStory = function () {
NewStoryService.showNewStoryModal();
};
});

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
* 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
* 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
@ -24,7 +24,7 @@
*/
angular.module('storyboard',
[ 'sb.services', 'sb.templates', 'sb.pages', 'sb.projects', 'sb.auth',
'sb.teams', 'sb.project_groups', 'ui.router']
'sb.story', 'ui.router', 'ui.bootstrap']
)
.config(function ($provide, $stateProvider, $urlRouterProvider,
$locationProvider, $httpProvider) {
@ -40,7 +40,8 @@ angular.module('storyboard',
$stateProvider
.state('index', {
url: '/',
templateUrl: 'app/templates/index.html'
templateUrl: 'app/templates/index.html',
controller: 'HomeController'
});
// Attach common request headers out of courtesy to the API

View File

@ -1,40 +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.
*/
/**
* The Storyboard team submodule handles most activity surrounding the
* creation and management of project teams.
*/
angular.module('sb.teams', ['ui.router', 'sb.services', 'sb.util'])
.config(function ($stateProvider, $urlRouterProvider) {
'use strict';
// URL Defaults.
$urlRouterProvider.when('/teams', '/teams/list');
// Set our page routes.
$stateProvider
.state('teams', {
abstract: true,
url: '/teams',
template: '<div ui-view></div>'
})
.state('teams.list', {
url: '/list',
templateUrl: 'app/templates/teams/list.html',
controller: 'TeamsListController'
});
});

View File

@ -21,15 +21,17 @@
<a href="#!/page/about">About</a>
</small>
</div>
<div class="col-sm-6">
<small class="text-right hidden-xs">
<div class="col-sm-6 text-right hidden-xs">
<small>
Licensed under the
<a href="http://www.apache.org/licenses/LICENSE-2.0"
target="_blank">
Apache License, Version 2.0
</a>
</small>
<small class="visible-xs">
</div>
<div class="col-xs-12 visible-xs">
<small>
Licensed under the
<a href="http://www.apache.org/licenses/LICENSE-2.0"
target="_blank">

View File

@ -14,48 +14,95 @@
~ under the License.
-->
<div class="container">
<div class="container" ng-controller="HeaderController">
<div class="navbar-header">
<!-- Expand menu button, only visible in mobile view. -->
<button class="navbar-toggle" type="button" data-toggle="collapse"
data-target=".sb-navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="#!/" class="navbar-brand">
<i class="icon icon_openstack"></i> Storyboard
</a>
<!-- New story button for the mobile view -->
<button type="button"
ng-click="newStory()"
class="visible-xs new-story-button btn btn-primary btn-sm navbar-btn pull-right"
>
<i class="fa fa-plus"></i>
New Story
</button>
<!-- Brand logo -->
<div class="navbar-brand">
<a href="#!/">
<i class="icon icon_openstack"></i> Storyboard
</a>
</div>
</div>
<nav class="collapse navbar-collapse sb-navbar-collapse" role="navigation">
<ul class="nav navbar-nav">
<li>
<a href="#!/project_groups">Project Groups</a>
</li>
<li>
<a href="#!/project">Projects</a>
</li>
<li>
<a href="#!/teams">Teams</a>
</li>
<li>
<a href="#!/stories">Stories</a>
</li>
<li>
<a href="#!/page/about">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li ng-show="isLoggedIn">
<a href="#!/profile">{{currentUser.firstName}}
{{currentUser.lastName}} <i class="fa fa-cog"></i></a>
</li>
<li ng-show="isLoggedIn">
<a href="#!/logout">Log out</a>
</li>
<li ng-hide="isLoggedIn">
<a href="#!/auth">Log in</a>
</li>
</li>
</ul>
</nav>
<ul class="nav navbar-nav collapse navbar-collapse sb-navbar-collapse">
<!-- User name, visible in XS only -->
<li class="visible-xs">
<p class="navbar-text" ng-show="isLoggedIn">
<i class="fa fa-user"></i>&nbsp;
{{currentUser.firstName}}
{{currentUser.lastName}}
</p>
</li>
<li>
<a href="#!/" active-path="^\/$">Overview</a>
</li>
<li>
<a href="#!/project" active-path="^\/project\/*">Projects</a>
</li>
<li>
<a href="#!/story" active-path="^\/story.*">Stories</a>
</li>
<!-- Divider, XS only -->
<li class="visible-xs">
<hr/>
</li>
<!-- Login/Logout button, XS only. -->
<li class="visible-xs">
<a href="#!/auth/login" ng-hide="isLoggedIn">
Log in
</a>
<a href="#!/auth/logout" ng-show="isLoggedIn">
Log out
</a>
</li>
</ul>
<!-- User dropdown menu for non-mobile views -->
<ul class="nav navbar-nav navbar-right hidden-xs">
<li>
<button type="button"
ng-click="newStory()"
class="hidden-xs btn btn-primary btn-sm navbar-btn">
<i class="fa fa-plus"></i>
New Story
</button>
</li>
<!-- Username and logout, non-XS only. -->
<li ng-show="isLoggedIn">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-user"></i>&nbsp;
{{currentUser.firstName}}
{{currentUser.lastName}}&nbsp;
<i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu">
<li>
<a href="#!/auth/logout">Logout</a>
</li>
</ul>
</li>
<!-- Login, non-XS only. -->
<li ng-hide="isLoggedIn">
<a href="#!/auth">Log in</a>
</li>
</ul>
</div>

View File

@ -1,19 +1,3 @@
<!--
~ 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-sm-12">
@ -22,9 +6,11 @@
<p class="lead">A task tracking system for inter-related
projects.</p>
<p>This page intentionally left blank. It is tentatively slated to
be replaced by a high-level overview of projects managed
by storyboard.</p>
<button type="button" class="btn btn-primary"
ng-click="newStory()">
<i class="fa fa-plus"></i>
New Story
</button>
</div>
</div>
</div>

View File

@ -0,0 +1,34 @@
<!--
~ 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="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?
</h2>
<p class="text-center lead">
This will set the project to a "deleted" state, and any stories and
tasks will no longer be visible.
</p>
<div class="text-center">
<a href="" class="btn btn-danger btn-disabled">
Feature not yet available
</a>
</div>
</div>
</div>

View File

@ -14,25 +14,44 @@
~ under the License.
-->
<div class="container" ng-show="isLoading">
<div class="col-xs-12">
<div class="row">
<p class="text-center">
<i class="fa fa-refresh fa-spin"></i>
<i class="fa fa-refresh fa-spin fa-lg"></i>
</p>
</div>
</div>
<div class="container" ng-hide="isLoading">
<div class="row">
<div class="col-xs-12">
<h1>{{project.name}}</h1>
<p>{{project.description}}</p>
<hr/>
</div>
</div>
<div class="row">
<div class="col-xs-12">
Project Detail List TBD.
</div>
<h1 class="no-border no-margin-bottom">Project detail:
{{project.name}}
</h1>
<ul class="nav nav-tabs nav-tabs-down nav-thick">
<li active-path="^\/project\/[0-9]+\/overview.*">
<a href="#!/project/1/overview">
Overview
</a>
</li>
<li active-path="^\/project\/[0-9]+\/stories.*">
<a href="#!/project/1/stories">
Stories
<span ng-show="!!projectStoryCount">
({{projectStoryCount}})
</span>
</a>
</li>
<li active-path="^\/project\/[0-9]+\/edit.*">
<a href="#!/project/1/edit">
Edit
</a>
</li>
<li active-path="^\/project\/[0-9]+\/delete.*">
<a href="#!/project/1/delete">
Delete
</a>
</li>
</ul>
<br/>
</div>
<div class="row" ui-view></div>
</div>

View File

@ -13,72 +13,52 @@
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<div class="container" ng-show="isLoading">
<div class="row">
<div class="col-xs-12">
<p class="text-center">
<i class="fa fa-refresh fa-2x fa-spin"></i>
</p>
</div>
</div>
<div class="container" ng-hide="isLoading">
<div class="row">
<div class="col-xs-12">
<h1>Project: {{project.name}}</h1>
<hr/>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<form class="form-horizontal" role="form" name="projectForm">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">
Project Name:
</label>
<div class="col-sm-10">
<input id="name"
type="text"
class="form-control"
ng-model="project.name"
required
placeholder="Project Name">
</div>
</div>
<div class="form-group">
<label for="description"
class="col-sm-2 control-label">
Project Description
</label>
<div class="col-sm-10">
<textarea id="description"
class="form-control"
ng-model="project.description"
required
placeholder="A brief project description">
</textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button"
ng-click="update()"
class="btn btn-primary"
ng-disabled="!projectForm.$valid">
Save Changes
</button>
<button ng-click="remove()"
class="btn btn-danger">
Delete Project
</button>
<a href="#!/project/list"
class="btn btn-default">
Cancel
</a>
</div>
</div>
</form>
</div>
<form class="form-horizontal" role="form" name="projectForm">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">
Project Name:
</label>
<div class="col-sm-10">
<input id="name"
type="text"
class="form-control"
ng-model="project.name"
required
placeholder="Project Name">
</div>
</div>
<div class="form-group">
<label for="description"
class="col-sm-2 control-label">
Project Description
</label>
<div class="col-sm-10">
<textarea id="description"
class="form-control"
ng-model="project.description"
required
placeholder="A brief project description">
</textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button"
ng-click="update()"
class="btn btn-primary"
ng-disabled="!projectForm.$valid">
Save Changes
</button>
<a href="#!/project/list"
class="btn btn-default">
Cancel
</a>
</div>
</div>
</form>
</div>
</div>

View File

@ -1,5 +1,5 @@
<!--
~ Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
~ 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
@ -13,41 +13,35 @@
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<div class="container">
<div class="row">
<div class="col-sm-8 col-md-9">
<h3 class="no-margin">
<a href="#!/project/new" class="btn btn-default">
<i class="fa fa-plus"></i>
</a>
Projects
</h3>
<br class="visible-xs"/>
</div>
<div class="col-sm-4 col-md-3">
<div class="input-group">
<input type="text" class="form-control"
placeholder="Search Projects"
ng-disabled="isSearching"
ng-enter="search()"
ng-model="searchQuery"/>
<span class="input-group-btn">
<button type="button" ng-click="search()"
ng-disabled="isSearching"
class="btn btn-default">
<i class="fa fa-refresh fa-spin"
ng-show="isSearching"></i>
<i class="fa fa-search"
ng-hide="isSearching"></i>
</button>
</span>
</div>
<div class="col-xs-12">
<h1>{{projectCount}} Projects</h1>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<hr/>
<div class="col-sm-3 col-xs-10">
<div class="btn-group btn-group-justified">
<div class="form-group has-feedback has-feedback-no-label">
<input type="text"
class="form-control input-sm"
placeholder="Search Projects"
ng-disabled="isSearching"
ng-enter="search()"
ng-model="searchQuery"/>
<span class="fa fa-search form-control-feedback"
ng-hide="isSearching"></span>
<span class="fa fa-refresh fa-spin form-control-feedback"
ng-show="isSearching"></span>
</div>
</div>
</div>
<div class="col-sm-9 col-xs-2">
<a href="#!/project/new"
class="pull-right btn btn-default btn-sm">
<i class="fa fa-plus"></i>
<span class="hidden-xs">New Project</span>
</a>
</div>
</div>
<div class="row">
@ -59,29 +53,47 @@
</p>
</div>
<table class="table table-striped table-hover table-responsive"
ng-hide="isSearching">
<p ng-show="!isSearching && projects.length == 0"
class="text-center text-warning">
<em> We were unable to find any projects.
Perhaps you would like to create one?</em>
</p>
<table class="table table-striped"
ng-hide="isSearching || projects.length == 0">
<thead>
<tr>
<th class="col-sm-10">
<small>Project Name</small>
</th>
<th class="col-sm-2 text-right">
<small>
<strong>Stories</strong><br/>
Opened / Total
</small>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="project in projects">
<td>
<div class="pull-right">
<a href="#!/project/{{project.id}}/edit">
<i class="fa fa-edit"></i>
<p><strong>
<a href="#!/project/{{project.id}}">
{{project.name}}
</a>
</div>
<a href="#!/project/{{project.id}}">
<strong>{{project.name}}</strong>
</a>
<br/>
{{project.description}}
</strong></p>
<small class="text-muted">
{{project.description}}
</small>
</td>
<td class="text-right">
<p class="text-muted">
{{project.openStories}} / {{project.totalStories}}
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -17,8 +17,7 @@
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Create a new project</h1>
<hr/>
<h1>New Project</h1>
</div>
</div>
<div class="row">

View File

@ -14,26 +14,13 @@
~ under the License.
-->
<div class="container" ng-show="isLoading">
<div class="row">
<div class="col-xs-12">
<p class="text-center">
<i class="fa fa-refresh fa-spin"></i>
<p ng-show="project.description">{{project.description}}</p>
<p ng-hide="project.description"
class="text-muted text-center">
No description available.
</p>
</div>
</div>
<div class="container" ng-hide="isLoading">
<div class="row">
<div class="col-xs-12">
<h1>{{projectGroup.name}}</h1>
<p>{{projectGroup.title}}</p>
<hr/>
</div>
</div>
<div class="row">
<div class="col-xs-12">
Project List TBD.
</div>
</div>
</div>

View File

@ -0,0 +1,97 @@
<!--
~ 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="col-sm-3 col-xs-10">
<div class="btn-group btn-group-justified">
<div class="form-group has-feedback has-feedback-no-label">
<input type="text"
class="form-control input-sm"
placeholder="Search Stories"
ng-disabled="isSearching"
ng-enter="search()"
ng-model="searchQuery"/>
<span class="fa fa-search form-control-feedback"
ng-hide="isSearching"></span>
<span class="fa fa-refresh fa-spin form-control-feedback"
ng-show="isSearching"></span>
</div>
</div>
</div>
<div class="col-sm-9 col-xs-2">
<a href=""
ng-click="newStory()"
class="pull-right btn btn-default btn-sm">
<i class="fa fa-plus"></i>
<span class="hidden-xs">New Story</span>
</a>
</div>
<div ng-show="!isSearching && stories.length == 0"
class="col-sm-12 text-center text-warning">
<hr/>
<p> We were unable to find any stories in this project.
Perhaps you would like to create one?</p>
</div>
<div class="col-sm-12">
<div ng-show="isSearching">
<hr/>
<p class="text-center">
<i class="fa fa-refresh fa-spin fa-lg"></i>
</p>
</div>
</div>
<table class="table table-striped"
ng-hide="isSearching || stories.length == 0">
<thead>
<tr>
<th class="col-sm-1">
<small>ID</small>
</th>
<th class="col-sm-7">
<small>Story Name</small>
</th>
<th class="col-sm-2 text-right">
<small>
<strong>Tasks</strong><br/>
Opened / Total
</small>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="story in stories">
<td>
<p><strong>
<a href="#!/story/{{story.id}}">{{story.id}}</a>
</strong></p>
</td>
<td>
<p><strong>
<a href="#!/story/{{story.id}}">{{story.title}}</a>
</strong></p>
<small class="text-muted">{{story.description}}</small>
</td>
<td class="text-right">
<p class="text-muted">
{{story.openTasks}} / {{story.totalTasks}}
</p>
</td>
</tr>
</tbody>
</table>

View File

@ -1,85 +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" ng-show="isLoading">
<div class="col-xs-12">
<p class="text-center">
<i class="fa fa-refresh fa-2x fa-spin"></i>
</p>
</div>
</div>
<div class="container" ng-hide="isLoading">
<div class="row">
<div class="col-xs-12">
<h1>Project Group: {{projectGroup.title}}</h1>
<hr/>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<form class="form-horizontal" role="form" name="projectForm">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">
Group Name:
</label>
<div class="col-sm-10">
<input id="name"
type="text"
class="form-control"
ng-model="projectGroup.name"
required
placeholder="Group Name">
</div>
</div>
<div class="form-group">
<label for="title"
class="col-sm-2 control-label">
Group Title
</label>
<div class="col-sm-10">
<input id="title"
type="text"
class="form-control"
ng-model="projectGroup.title"
required
placeholder="Group Title">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button"
ng-click="update()"
class="btn btn-primary"
ng-disabled="!projectForm.$valid">
Save Changes
</button>
<button ng-click="remove()"
class="btn btn-danger">
Delete Group
</button>
<a href="#!/project_groups/list"
class="btn btn-default">
Cancel
</a>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -1,87 +0,0 @@
<!--
~ Copyright (c) 2013 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-sm-8 col-md-9">
<h3 class="no-margin">
<a href="#!/project_groups/new" class="btn btn-default">
<i class="fa fa-plus"></i>
</a>
Project Groups
</h3>
<br class="visible-xs"/>
</div>
<div class="col-sm-4 col-md-3">
<div class="input-group">
<input type="text" class="form-control"
placeholder="Search Project Groups"
ng-disabled="isSearching"
ng-enter="search()"
ng-model="searchQuery"/>
<span class="input-group-btn">
<button type="button" ng-click="search()"
ng-disabled="isSearching"
class="btn btn-default">
<i class="fa fa-refresh fa-spin"
ng-show="isSearching"></i>
<i class="fa fa-search"
ng-hide="isSearching"></i>
</button>
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<hr/>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div ng-show="isSearching">
<hr/>
<p class="text-center">
<i class="fa fa-refresh fa-spin fa-lg"></i>
</p>
</div>
<table class="table table-striped table-hover table-responsive"
ng-hide="isSearching">
<tbody>
<tr ng-repeat="projectGroup in projectGroups">
<td>
<div class="pull-right">
<a href="#!/project_groups/{{projectGroup.id}}/edit">
<i class="fa fa-edit"></i>
</a>
</div>
<a href="#!/project_groups/{{projectGroup.id}}">
<strong>{{projectGroup.name}}</strong>
</a>
<br/>
{{projectGroup.title}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -1,74 +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>Create a new project group</h1>
<hr/>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<form class="form-horizontal" role="form" name="projectGroupForm">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">
Group Name:
</label>
<div class="col-sm-10">
<input id="name"
type="text"
class="form-control"
ng-model="newProjectGroup.name"
required
placeholder="Group Name">
</div>
</div>
<div class="form-group">
<label for="title"
class="col-sm-2 control-label">
Group Title:
</label>
<div class="col-sm-10">
<input id="title"
type="text"
class="form-control"
ng-model="newProjectGroup.title"
required
placeholder="Group Title">
</textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button"
ng-click="createProjectGroup()"
class="btn btn-primary"
ng-disabled="!projectGroupForm.$valid">
Create project group
</button>
<a href="#!/project_groups/list"
class="btn btn-default">
Cancel
</a>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,34 @@
<!--
~ 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="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 story?
</h2>
<p class="text-center lead">
This will set the story to a "deleted" state, and any
tasks will no longer be visible.
</p>
<div class="text-center">
<a href="" class="btn btn-danger btn-disabled">
Feature not yet available
</a>
</div>
</div>
</div>

View File

@ -0,0 +1,39 @@
<!--
~ 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">
<h1 class="no-border no-margin-bottom">Story detail: {{story.title}}</h1>
<ul class="nav nav-tabs nav-tabs-down nav-thick">
<li active-path="^\/story\/[0-9]+\/overview.*">
<a href="#!/story/{{story.id}}/overview">
Overview
</a>
</li>
<li active-path="^\/story\/[0-9]+\/edit.*">
<a href="#!/story/{{story.id}}/edit">
Edit
</a>
</li>
<li active-path="^\/story\/[0-9]+\/delete.*">
<a href="#!/story/{{story.id}}/delete">
Delete
</a>
</li>
</ul>
<br/>
<div ui-view></div>
</div>

View File

@ -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.
-->
<div class="row">
<div class="col-xs-12">
<form class="form-horizontal" role="form" name="storyForm">
<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="story.title"
required
placeholder="Story Title">
</div>
</div>
<div class="form-group">
<label for="description"
class="col-sm-2 control-label">
Story Description
</label>
<div class="col-sm-10">
<textarea id="description"
class="form-control"
ng-model="story.description"
required
placeholder="A brief story description">
</textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button"
ng-click="update()"
class="btn btn-primary"
ng-disabled="!storyForm.$valid">
Save Changes
</button>
<a href="#!/story/list"
class="btn btn-default">
Cancel
</a>
</div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,106 @@
<!--
~ 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>{{storyCount}} Stories</h1>
</div>
</div>
<div class="row">
<div class="col-sm-3 col-xs-10">
<div class="btn-group btn-group-justified">
<div class="form-group has-feedback has-feedback-no-label">
<input type="text"
class="form-control input-sm"
placeholder="Search Stories"
ng-disabled="isSearching"
ng-enter="search()"
ng-model="searchQuery"/>
<span class="fa fa-search form-control-feedback"
ng-hide="isSearching"></span>
<span class="fa fa-refresh fa-spin form-control-feedback"
ng-show="isSearching"></span>
</div>
</div>
</div>
<div class="col-sm-9 col-xs-2">
<a href=""
ng-click="newStory()"
class="pull-right btn btn-default btn-sm">
<i class="fa fa-plus"></i>
<span class="hidden-xs">New Story</span>
</a>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div ng-show="isSearching">
<hr/>
<p class="text-center">
<i class="fa fa-refresh fa-spin fa-lg"></i>
</p>
</div>
<p ng-show="!isSearching && stories.length == 0"
class="text-center text-warning">
<em> We were unable to find any stories.
Perhaps you would like to create one?</em>
</p>
<table class="table table-striped"
ng-hide="isSearching || stories.length == 0">
<thead>
<tr>
<th class="col-sm-1">
<small>ID</small>
</th>
<th class="col-sm-7">
<small>Story Name</small>
</th>
<th class="col-sm-2 text-right">
<small>
<strong>Tasks</strong><br/>
Opened / Total
</small>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="story in stories">
<td>
<p><strong>
<a href="#!/story/{{story.id}}">{{story.id}}</a>
</strong></p>
</td>
<td>
<p><strong>
<a href="#!/story/{{story.id}}">{{story.title}}</a>
</strong></p>
<small class="text-muted">{{story.description}}</small>
</td>
<td class="text-right">
<p class="text-muted">
{{story.openTasks}} / {{story.totalTasks}}
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,72 @@
<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 Story</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-xs-12">
<form class="form-horizontal" role="form" name="storyForm">
<div class="form-group">
<label for="project" class="col-sm-2 control-label">
Project:
</label>
<div class="col-sm-10">
<select ng-model="story.project"
id="project"
name="project"
class="form-control"
required
ng-options="p.id as p.name for p in projects"/>
</div>
</div>
<div class="form-group">
<label for="name" class="col-sm-2 control-label">
Title:
</label>
<div class="col-sm-10">
<input id="name"
type="text"
class="form-control"
ng-model="story.title"
required
placeholder="Story Title">
</div>
</div>
<div class="form-group">
<label for="description"
class="col-sm-2 control-label">
Description
</label>
<div class="col-sm-10">
<textarea id="description"
class="form-control"
ng-model="story.description"
required
placeholder="A brief story description">
</textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button"
class="btn btn-primary"
ng-click="save()"
ng-disabled="!storyForm.$valid">
Save Changes
</button>
<button type="button"
ng-click="close()"
class="btn btn-default">
Cancel
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,120 @@
<!--
~ 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="row">
<div class="col-sm-9 col-xs-11">
<p ng-show="story.description">
{{story.description}}
</p>
<em ng-hide="story.description"
class="text-muted">
No description available
</em>
</div>
<div class="col-sm-3 col-xs-1">
<a href=""
ng-click="showAddTaskForm = !showAddTaskForm"
class="pull-right btn btn-default btn-sm">
<span ng-hide="showAddTaskForm">
<i class="fa fa-plus"></i>&nbsp;
<span class="hidden-xs">Add Task</span>
</span>
<span ng-show="showAddTaskForm">
<i class="fa fa-minus"></i>&nbsp;
<span class="hidden-xs">Add Task</span>
</span>
</a>
</div>
</div>
<div class="row" ng-show="showAddTaskForm">
<br/>
<div class="col-sm-12">
<div class="well">
<form role="form" name="taskForm">
<div class="form-group row">
<label for="title" class="col-sm-2 control-label">
Task Title:
</label>
<div class="col-sm-10">
<input id="title"
type="text"
class="form-control"
ng-model="newTask.title"
required
placeholder="Task Title">
</div>
</div>
<div class="row">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default"
ng-click="addTask()">
Add task
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<table class="table table-striped">
<thead>
<tr>
<th class="col-sm-1">
<small>ID</small>
</th>
<th class="col-sm-6">
<small>Title</small>
</th>
<th class="col-sm-3">
<small>Project</small>
</th>
<th class="col-sm-2">
<small>Status</small>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="task in tasks">
<td>
<p><strong>
<a href="">{{task.id}}</a>
</strong></p>
</td>
<td>
<p><strong>
<a href="">{{task.title}}</a>
</strong></p>
<small class="text-muted">{{task.description}}</small>
</td>
<td>
<p><strong>
<a href="">Not Yet Implemented</a>
</strong></p>
</td>
<td>
<p class="text-success">{{task.status}}</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -1,87 +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-sm-8 col-md-9">
<h3 class="no-margin">
<a href="#!/team/new" class="btn btn-default">
<i class="fa fa-plus"></i>
</a>
Teams
</h3>
<br class="visible-xs"/>
</div>
<div class="col-sm-4 col-md-3">
<div class="input-group">
<input type="text" class="form-control"
placeholder="Search Teams"
ng-disabled="isSearching"
ng-enter="search()"
ng-model="searchQuery"/>
<span class="input-group-btn">
<button type="button" ng-click="search()"
ng-disabled="isSearching"
class="btn btn-default">
<i class="fa fa-refresh fa-spin"
ng-show="isSearching"></i>
<i class="fa fa-search"
ng-hide="isSearching"></i>
</button>
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<hr/>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div ng-show="isSearching">
<hr/>
<p class="text-center">
<i class="fa fa-refresh fa-spin fa-lg"></i>
</p>
</div>
<table class="table table-striped table-hover table-responsive"
ng-hide="isSearching">
<tbody>
<tr ng-repeat="team in teams">
<td>
<div class="pull-right">
<a href="#!/team/{{team.id}}/edit">
<i class="fa fa-edit"></i>
</a>
</div>
<a href="#!/team/{{team.id}}">
<strong>{{team.name}}</strong>
</a>
<br/>
{{team.description}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -30,13 +30,18 @@ angular.module('sb.util').directive('activePath',
var activePath = attrs.activePath;
function setActivePath() {
var isActive = activePath === $location.path();
element.toggleClass('active', isActive);
var path = $location.path();
var isMatchedPath = path.match(activePath) !== null;
element.toggleClass('active', isMatchedPath);
}
$scope.$on('$destroy',
$rootScope.$on('$stateChangeSuccess', setActivePath)
);
// This is angularjs magic, the return method from any $on
// binding will return a function that will disconnect
// that binding.
var disconnectBinding =
$rootScope.$on('$stateChangeSuccess', setActivePath);
$scope.$on('$destroy', disconnectBinding);
// INIT
setActivePath();

View File

@ -30,6 +30,8 @@
<!-- build:js(bower_components) js/libs.js -->
<script src="jquery/jquery.js"></script>
<script src="angular/angular.js"></script>
<script src="angular-bootstrap/ui-bootstrap.js"></script>
<script src="angular-bootstrap/ui-bootstrap-tpls.js"></script>
<script src="angular-ui-router/release/angular-ui-router.js"></script>
<script src="angular-resource/angular-resource.js"></script>
<script src="angular-mocks/angular-mocks.js"></script>
@ -48,7 +50,31 @@
<header class="navbar navbar-default navbar-fixed-top"
ng-include src="'app/templates/header.html'">
</header>
<div ui-view></div>
<div ui-view class="main"></div>
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">&times;</button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
<p>One fine body&hellip;</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default"
data-dismiss="modal">Close
</button>
<button type="button" class="btn btn-primary">Save changes
</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<footer class="container" ng-include src="'app/templates/footer.html'">
</footer>
</body>

View File

@ -16,12 +16,17 @@
/**
* Styles specific to the overall application.
*/
@import url(http://fonts.googleapis.com/css?family=PT+Sans:400,700,400italic,700italic&subset=latin,cyrillic-ext,latin-ext,cyrillic);
body {
margin-top: 70px;
.main {
// Min height set so that the footer doesn't bounce around as much,
// but there's only so much we can do.
min-height: 500px;
}
}
i.icon {
line-height: .5em;
}
}

View File

@ -18,16 +18,132 @@
* Generic overrides and addons for bootstrap.
*/
h1, h2, h3, h4, h5, h6 {
&.no-margin {
margin: 0px;
}
&.no-border {
border: none;
}
&.no-margin-bottom {
margin-bottom: 0px;
}
}
h1 {
padding-bottom: .3em;
border-bottom: 2px solid @gray-lighter;
}
.navbar {
#gradient > .vertical(@white, @gray-lighter);
.box-shadow( 0 1px 3px rgba(0,0,0,.25))
.box-shadow(0 1px 3px rgba(0, 0, 0, .25));
// The new story button in the mobile view needs a little additional margin.
.new-story-button {
margin-right: 5px;
}
.navbar-nav {
li {
.active {
@media (max-width: @screen-sm) {
border-left: 5px solid @brand-primary;
color: @gray-darker;
}
@media (min-width: @screen-sm) {
border-bottom: 3px solid @brand-primary;
margin-bottom: -2px;
padding-bottom: (@navbar-padding-vertical - 2);
color: @gray-darker;
}
}
&.visible-xs {
hr {
border-top: 1px solid @gray-light;
margin: 5px 10px 5px 10px;
}
}
}
}
}
.navbar-brand {
color: @brand-primary;
a:hover {
text-decoration: none;
}
}
.has-feedback {
// Sibling selectors for inputs of different sizes.
.input-sm + .form-control-feedback {
line-height: 30px;
}
&.has-feedback-no-label {
.form-control-feedback {
top: 0px;
}
}
}
.nav-tabs {
&.nav-tabs-down {
border-bottom: none;
border-top: 1px solid @nav-tabs-border-color;
> li {
margin-bottom: 0px;
margin-top: -1px;
// Actual tabs (as links)
> a {
border-width: 1px;
border-radius: 0 0 @border-radius-base @border-radius-base;
&:hover {
border-bottom-color: @nav-tabs-link-hover-border-color;
border-top-color: @nav-tabs-border-color;
}
}
// Active state, and its :hover to override normal :hover
&.active > a {
&,
&:hover,
&:focus {
border-bottom-color: @nav-tabs-active-link-hover-border-color;
border-top-color: transparent;
}
}
}
}
&.nav-thick {
border-width: 2px;
> li {
margin-bottom: 0px;
margin-top: -2px;
> a {
border-width: 2px;
}
// Active state, and its :hover to override normal :hover
&.active > a {
&,
&:hover,
&:focus {
border-width: 2px;
}
}
}
}
}

View File

@ -1,604 +0,0 @@
//
// A verbatim copy of the bootstrap variables file, included in our source
// so we can override colors, padding, fonts, what have you.
// --------------------------------------------------
// Global values
// --------------------------------------------------
// Grays
// -------------------------
@gray-darker: lighten(#000, 13.5%);
// #222
@gray-dark: lighten(#000, 20%);
// #333
@gray: lighten(#000, 33.5%);
// #555
@gray-light: lighten(#000, 60%);
// #999
@gray-lighter: lighten(#000, 93.5%);
// #eee
@white: #FFFFFF;
// Brand colors
// -------------------------
@brand-primary: #C43422;
@brand-success: #5cb85c;
@brand-warning: #f0ad4e;
@brand-danger: #d9534f;
@brand-info: #5bc0de;
// Scaffolding
// -------------------------
@body-bg: #fff;
@text-color: @gray-dark;
// Links
// -------------------------
@link-color: @brand-primary;
@link-hover-color: lighten(@link-color, 10%);
// Typography
// -------------------------
@font-family-title: "PT Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
@font-family-serif: Georgia, "Times New Roman", Times, serif;
@font-family-monospace: Monaco, Menlo, Consolas, "Courier New", monospace;
@font-family-base: @font-family-sans-serif;
@font-size-base: 14px;
@font-size-large: ceil(@font-size-base * 1.25);
// ~18px
@font-size-small: ceil(@font-size-base * 0.85);
// ~12px
@line-height-base: 1.428571429;
// 20/14
@line-height-computed: floor(@font-size-base * @line-height-base);
// ~20px
@headings-font-family: @font-family-title;
@headings-font-weight: 500;
@headings-line-height: 1.1;
// Iconography
// -------------------------
@icon-font-path: "../fonts/";
@icon-font-name: "glyphicons-halflings-regular";
// Components
// -------------------------
// Based on 14px font-size and 1.428 line-height (~20px to start)
@padding-base-vertical: 6px;
@padding-base-horizontal: 12px;
@padding-large-vertical: 10px;
@padding-large-horizontal: 16px;
@padding-small-vertical: 5px;
@padding-small-horizontal: 10px;
@line-height-large: 1.33;
@line-height-small: 1.5;
@border-radius-base: 4px;
@border-radius-large: 6px;
@border-radius-small: 3px;
@component-active-bg: @brand-primary;
@caret-width-base: 4px;
@caret-width-large: 5px;
// Tables
// -------------------------
@table-cell-padding: 8px;
@table-condensed-cell-padding: 5px;
@table-bg: transparent;
// overall background-color
@table-bg-accent: #f9f9f9;
// for striping
@table-bg-hover: #f5f5f5;
@table-bg-active: @table-bg-hover;
@table-border-color: #ddd;
// table and cell border
// Buttons
// -------------------------
@btn-font-weight: normal;
@btn-default-color: #333;
@btn-default-bg: #fff;
@btn-default-border: #ccc;
@btn-primary-color: #fff;
@btn-primary-bg: @brand-primary;
@btn-primary-border: darken(@btn-primary-bg, 5%);
@btn-success-color: #fff;
@btn-success-bg: @brand-success;
@btn-success-border: darken(@btn-success-bg, 5%);
@btn-warning-color: #fff;
@btn-warning-bg: @brand-warning;
@btn-warning-border: darken(@btn-warning-bg, 5%);
@btn-danger-color: #fff;
@btn-danger-bg: @brand-danger;
@btn-danger-border: darken(@btn-danger-bg, 5%);
@btn-info-color: #fff;
@btn-info-bg: @brand-info;
@btn-info-border: darken(@btn-info-bg, 5%);
@btn-link-disabled-color: @gray-light;
// Forms
// -------------------------
@input-bg: #fff;
@input-bg-disabled: @gray-lighter;
@input-color: @gray;
@input-border: #ccc;
@input-border-radius: @border-radius-base;
@input-border-focus: #66afe9;
@input-color-placeholder: @gray-light;
@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2);
@input-height-large: (floor(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
@legend-color: @gray-dark;
@legend-border-color: #e5e5e5;
@input-group-addon-bg: @gray-lighter;
@input-group-addon-border-color: @input-border;
// Dropdowns
// -------------------------
@dropdown-bg: #fff;
@dropdown-border: rgba(0, 0, 0, .15);
@dropdown-fallback-border: #ccc;
@dropdown-divider-bg: #e5e5e5;
@dropdown-link-active-color: #fff;
@dropdown-link-active-bg: @component-active-bg;
@dropdown-link-color: @gray-dark;
@dropdown-link-hover-color: #fff;
@dropdown-link-hover-bg: @dropdown-link-active-bg;
@dropdown-link-disabled-color: @gray-light;
@dropdown-header-color: @gray-light;
@dropdown-caret-color: #000;
// COMPONENT VARIABLES
// --------------------------------------------------
// Z-index master list
// -------------------------
// Used for a bird's eye view of components dependent on the z-axis
// Try to avoid customizing these :)
@zindex-navbar: 1000;
@zindex-dropdown: 1000;
@zindex-popover: 1010;
@zindex-tooltip: 1030;
@zindex-navbar-fixed: 1030;
@zindex-modal-background: 1040;
@zindex-modal: 1050;
// Media queries breakpoints
// --------------------------------------------------
// Extra small screen / phone
@screen-xs: 480px;
@screen-phone: @screen-xs;
// Small screen / tablet
@screen-sm: 768px;
@screen-tablet: @screen-sm;
// Medium screen / desktop
@screen-md: 992px;
@screen-desktop: @screen-md;
// Large screen / wide desktop
@screen-lg: 1200px;
@screen-lg-desktop: @screen-lg;
// So media queries don't overlap when required, provide a maximum
@screen-xs-max: (@screen-sm - 1);
@screen-sm-max: (@screen-md - 1);
@screen-md-max: (@screen-lg - 1);
// Grid system
// --------------------------------------------------
// Number of columns in the grid system
@grid-columns: 12;
// Padding, to be divided by two and applied to the left and right of all columns
@grid-gutter-width: 30px;
// Point at which the navbar stops collapsing
@grid-float-breakpoint: @screen-tablet;
// Navbar
// -------------------------
// Basics of a navbar
@navbar-height: 40px;
@navbar-margin-bottom: @line-height-computed;
@navbar-default-color: #777;
@navbar-default-bg: #f8f8f8;
@navbar-default-border: @gray-lighter;
@navbar-border-radius: @border-radius-base;
@navbar-padding-horizontal: floor(@grid-gutter-width / 2);
@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2);
// Navbar links
@navbar-default-link-color: #777;
@navbar-default-link-hover-color: #333;
@navbar-default-link-hover-bg: transparent;
@navbar-default-link-active-color: #555;
@navbar-default-link-active-bg: darken(@navbar-default-bg, 6.5%);
@navbar-default-link-disabled-color: #ccc;
@navbar-default-link-disabled-bg: transparent;
// Navbar brand label
@navbar-default-brand-color: @brand-primary;
@navbar-default-brand-hover-color: lighten(@brand-primary, 10%);
@navbar-default-brand-hover-bg: transparent;
// Navbar toggle
@navbar-default-toggle-hover-bg: #ddd;
@navbar-default-toggle-icon-bar-bg: #ccc;
@navbar-default-toggle-border-color: #ddd;
// Inverted navbar
//
// Reset inverted navbar basics
@navbar-inverse-color: @gray-light;
@navbar-inverse-bg: #222;
@navbar-inverse-border: darken(@navbar-inverse-bg, 10%);
// Inverted navbar links
@navbar-inverse-link-color: @gray-light;
@navbar-inverse-link-hover-color: #fff;
@navbar-inverse-link-hover-bg: transparent;
@navbar-inverse-link-active-color: @navbar-inverse-link-hover-color;
@navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%);
@navbar-inverse-link-disabled-color: #444;
@navbar-inverse-link-disabled-bg: transparent;
// Inverted navbar brand label
@navbar-inverse-brand-color: @navbar-inverse-link-color;
@navbar-inverse-brand-hover-color: #fff;
@navbar-inverse-brand-hover-bg: transparent;
// Inverted navbar search
// Normal navbar needs no special styles or vars
@navbar-inverse-search-bg: lighten(@navbar-inverse-bg, 25%);
@navbar-inverse-search-bg-focus: #fff;
@navbar-inverse-search-border: @navbar-inverse-bg;
@navbar-inverse-search-placeholder-color: #ccc;
// Inverted navbar toggle
@navbar-inverse-toggle-hover-bg: #333;
@navbar-inverse-toggle-icon-bar-bg: #fff;
@navbar-inverse-toggle-border-color: #333;
// Navs
// -------------------------
@nav-link-padding: 10px 15px;
@nav-link-hover-bg: @gray-lighter;
@nav-disabled-link-color: @gray-light;
@nav-disabled-link-hover-color: @gray-light;
@nav-open-link-hover-color: #fff;
@nav-open-caret-border-color: #fff;
// Tabs
@nav-tabs-border-color: #ddd;
@nav-tabs-link-hover-border-color: @gray-lighter;
@nav-tabs-active-link-hover-bg: @body-bg;
@nav-tabs-active-link-hover-color: @gray;
@nav-tabs-active-link-hover-border-color: #ddd;
@nav-tabs-justified-link-border-color: #ddd;
@nav-tabs-justified-active-link-border-color: @body-bg;
// Pills
@nav-pills-active-link-hover-bg: @component-active-bg;
@nav-pills-active-link-hover-color: #fff;
// Pagination
// -------------------------
@pagination-bg: #fff;
@pagination-border: #ddd;
@pagination-hover-bg: @gray-lighter;
@pagination-active-bg: @brand-primary;
@pagination-active-color: #fff;
@pagination-disabled-color: @gray-light;
// Pager
// -------------------------
@pager-border-radius: 15px;
@pager-disabled-color: @gray-light;
// Jumbotron
// -------------------------
@jumbotron-padding: 30px;
@jumbotron-color: inherit;
@jumbotron-bg: @gray-lighter;
@jumbotron-heading-color: inherit;
// Form states and alerts
// -------------------------
@state-warning-text: #c09853;
@state-warning-bg: #fcf8e3;
@state-warning-border: darken(spin(@state-warning-bg, -10), 3%);
@state-danger-text: #b94a48;
@state-danger-bg: #f2dede;
@state-danger-border: darken(spin(@state-danger-bg, -10), 3%);
@state-success-text: #468847;
@state-success-bg: #dff0d8;
@state-success-border: darken(spin(@state-success-bg, -10), 5%);
@state-info-text: #3a87ad;
@state-info-bg: #d9edf7;
@state-info-border: darken(spin(@state-info-bg, -10), 7%);
// Tooltips
// -------------------------
@tooltip-max-width: 200px;
@tooltip-color: #fff;
@tooltip-bg: #000;
@tooltip-arrow-width: 5px;
@tooltip-arrow-color: @tooltip-bg;
// Popovers
// -------------------------
@popover-bg: #fff;
@popover-max-width: 276px;
@popover-border-color: rgba(0, 0, 0, .2);
@popover-fallback-border-color: #ccc;
@popover-title-bg: darken(@popover-bg, 3%);
@popover-arrow-width: 10px;
@popover-arrow-color: #fff;
@popover-arrow-outer-width: (@popover-arrow-width + 1);
@popover-arrow-outer-color: rgba(0, 0, 0, .25);
@popover-arrow-outer-fallback-color: #999;
// Labels
// -------------------------
@label-default-bg: @gray-light;
@label-primary-bg: @brand-primary;
@label-success-bg: @brand-success;
@label-info-bg: @brand-info;
@label-warning-bg: @brand-warning;
@label-danger-bg: @brand-danger;
@label-color: #fff;
@label-link-hover-color: #fff;
// Modals
// -------------------------
@modal-inner-padding: 20px;
@modal-title-padding: 15px;
@modal-title-line-height: @line-height-base;
@modal-content-bg: #fff;
@modal-content-border-color: rgba(0, 0, 0, .2);
@modal-content-fallback-border-color: #999;
@modal-backdrop-bg: #000;
@modal-header-border-color: #e5e5e5;
@modal-footer-border-color: @modal-header-border-color;
// Alerts
// -------------------------
@alert-padding: 15px;
@alert-border-radius: @border-radius-base;
@alert-link-font-weight: bold;
@alert-success-bg: @state-success-bg;
@alert-success-text: @state-success-text;
@alert-success-border: @state-success-border;
@alert-info-bg: @state-info-bg;
@alert-info-text: @state-info-text;
@alert-info-border: @state-info-border;
@alert-warning-bg: @state-warning-bg;
@alert-warning-text: @state-warning-text;
@alert-warning-border: @state-warning-border;
@alert-danger-bg: @state-danger-bg;
@alert-danger-text: @state-danger-text;
@alert-danger-border: @state-danger-border;
// Progress bars
// -------------------------
@progress-bg: #f5f5f5;
@progress-bar-color: #fff;
@progress-bar-bg: @brand-primary;
@progress-bar-success-bg: @brand-success;
@progress-bar-warning-bg: @brand-warning;
@progress-bar-danger-bg: @brand-danger;
@progress-bar-info-bg: @brand-info;
// List group
// -------------------------
@list-group-bg: #fff;
@list-group-border: #ddd;
@list-group-border-radius: @border-radius-base;
@list-group-hover-bg: #f5f5f5;
@list-group-active-color: #fff;
@list-group-active-bg: @component-active-bg;
@list-group-active-border: @list-group-active-bg;
@list-group-link-color: #555;
@list-group-link-heading-color: #333;
// Panels
// -------------------------
@panel-bg: #fff;
@panel-inner-border: #ddd;
@panel-border-radius: @border-radius-base;
@panel-footer-bg: #f5f5f5;
@panel-default-text: @gray-dark;
@panel-default-border: #ddd;
@panel-default-heading-bg: #f5f5f5;
@panel-primary-text: #fff;
@panel-primary-border: @brand-primary;
@panel-primary-heading-bg: @brand-primary;
@panel-success-text: @state-success-text;
@panel-success-border: @state-success-border;
@panel-success-heading-bg: @state-success-bg;
@panel-warning-text: @state-warning-text;
@panel-warning-border: @state-warning-border;
@panel-warning-heading-bg: @state-warning-bg;
@panel-danger-text: @state-danger-text;
@panel-danger-border: @state-danger-border;
@panel-danger-heading-bg: @state-danger-bg;
@panel-info-text: @state-info-text;
@panel-info-border: @state-info-border;
@panel-info-heading-bg: @state-info-bg;
// Thumbnails
// -------------------------
@thumbnail-padding: 4px;
@thumbnail-bg: @body-bg;
@thumbnail-border: #ddd;
@thumbnail-border-radius: @border-radius-base;
@thumbnail-caption-color: @text-color;
@thumbnail-caption-padding: 9px;
// Wells
// -------------------------
@well-bg: #f5f5f5;
// Badges
// -------------------------
@badge-color: #fff;
@badge-link-hover-color: #fff;
@badge-bg: @gray-light;
@badge-active-color: @link-color;
@badge-active-bg: #fff;
@badge-font-weight: bold;
@badge-line-height: 1;
@badge-border-radius: 10px;
// Breadcrumbs
// -------------------------
@breadcrumb-bg: #f5f5f5;
@breadcrumb-color: #ccc;
@breadcrumb-active-color: @gray-light;
// Carousel
// ------------------------
@carousel-text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
@carousel-control-color: #fff;
@carousel-control-width: 15%;
@carousel-control-opacity: .5;
@carousel-control-font-size: 20px;
@carousel-indicator-active-bg: #fff;
@carousel-indicator-border-color: #fff;
@carousel-caption-color: #fff;
// Close
// ------------------------
@close-color: #000;
@close-font-weight: bold;
@close-text-shadow: 0 1px 0 #fff;
// Code
// ------------------------
@code-color: #c7254e;
@code-bg: #f9f2f4;
@pre-bg: #f5f5f5;
@pre-color: @gray-dark;
@pre-border-color: #ccc;
@pre-scrollable-max-height: 340px;
// Type
// ------------------------
@text-muted: @gray-light;
@abbr-border-color: @gray-light;
@headings-small-color: @gray-light;
@blockquote-small-color: @gray-light;
@blockquote-border-color: @gray-lighter;
@page-header-border-color: @gray-lighter;
// Miscellaneous
// -------------------------
// Hr border color
@hr-border: @gray-lighter;
// Horizontal forms & lists
@component-offset-horizontal: 180px;
// Container sizes
// --------------------------------------------------
// Small screen / tablet
@container-tablet: ((720px + @grid-gutter-width));
// Medium screen / desktop
@container-desktop: ((940px + @grid-gutter-width));
// Large screen / wide desktop
@container-lg-desktop: ((1140px + @grid-gutter-width));

View File

@ -23,8 +23,9 @@
// Library inclusions
@import './bootstrap.less';
@import './font-awesome.less';
// Custom variable overrides
@import './bootstrap_variable_overrides.less';
// Variables
@import './variables.less';
// Addons to the bootstrap theme.
@import './bootstrap_addons.less';
// Add our own custom icon font.
@import './custom_font_icons.less';

31
src/styles/variables.less Normal file
View File

@ -0,0 +1,31 @@
// A list of specific variable overrides. Since bootstrap is a fairly
// fast moving project, it is recommended we only include those variables which
// we are adding/overriding.
// ============================== Custom Variables =============================
@white: #FFFFFF;
@black: #000000;
// ============================== Bootstrap Overrides ==========================
// Brand colors
// -------------------------
@brand-primary: #C43422;
// Typography
// -------------------------
@font-family-title: "Helvetica Neue", Helvetica, Arial, sans-serif;
@headings-font-family: @font-family-title;
@font-size-h1: (floor(@font-size-base * 2.15));
@font-size-h2: (floor(@font-size-base * 1.7));
@font-size-h3: (ceil(@font-size-base * 1.5));
@font-size-h4: (ceil(@font-size-base * 1.25));
@font-size-h5: (@font-size-base);
@font-size-h6: (ceil(@font-size-base * 0.85));
// Navbar
// -------------------------
// Basics of a navbar
@navbar-height: 40px;

View File

@ -1,29 +0,0 @@
/*
* Copyright (c) 2013 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.
*/
describe('Storyboard Login Routes', function () {
'use strict';
it('should redirect /auth to /auth/provider/list', function () {
browser.get('http://localhost:9000/#!/auth');
expect(browser.getCurrentUrl()).toContain('#!/auth/provider/list');
});
it('should redirect /auth/provider to /auth/provider/list', function () {
browser.get('http://localhost:9000/#!/auth/provider');
expect(browser.getCurrentUrl()).toContain('#!/auth/provider/list');
});
});

View File

@ -0,0 +1,24 @@
/*
* 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.
*/
describe('Storyboard Project Routes', function () {
'use strict';
it('should redirect /project to /project/list', function () {
browser.get('http://localhost:9000/#!/project');
expect(browser.getCurrentUrl()).toContain('#!/project/list');
});
});

View File

@ -0,0 +1,24 @@
/*
* 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.
*/
describe('Storyboard Story Routes', function () {
'use strict';
it('should redirect /story to /story/list', function () {
browser.get('http://localhost:9000/#!/story');
expect(browser.getCurrentUrl()).toContain('#!/story/list');
});
});

View File

@ -38,9 +38,9 @@ describe('storyboardApiSignature', function () {
});
});
it('should declare a search method', function () {
it('should declare a query method', function () {
inject(function (storyboardApiSignature) {
expect(storyboardApiSignature.search).toBeTruthy();
expect(storyboardApiSignature.query).toBeTruthy();
});
});
@ -68,10 +68,10 @@ describe('storyboardApiSignature', function () {
expect(storyboardApiSignature.delete.method).toEqual('DELETE');
});
});
it('should use GET to search', function () {
it('should use GET to query', function () {
inject(function (storyboardApiSignature) {
expect(storyboardApiSignature.search).toBeTruthy();
expect(storyboardApiSignature.search.method).toEqual('GET');
expect(storyboardApiSignature.query).toBeTruthy();
expect(storyboardApiSignature.query.method).toEqual('GET');
});
});
@ -81,7 +81,7 @@ describe('storyboardApiSignature', function () {
var Resource = $resource('/path/:id',
{id: '@id'},
storyboardApiSignature);
expect(Resource.search).toBeTruthy();
expect(Resource.query).toBeTruthy();
expect(Resource.read).toBeTruthy();
var resourceInstance = new Resource();