From 01bd40c183b9b48ca062ee84d02c73f6f46dfa8a Mon Sep 17 00:00:00 2001 From: Adam Coldrick Date: Wed, 7 Oct 2015 13:38:07 +0000 Subject: [PATCH] Add a page to view a worklist Add a worklist detail page, and various other modal templates for interacting with the worklist. Also add ng-sortable[0] as a new dependency. This is used to allow drag-and-drop rearrangement of the worklist contents. [0]: https://github.com/a5hik/ng-sortable Change-Id: I5cc85e687f9ce60db158168a9f4c8325b1a022f6 --- Gruntfile.js | 3 +- bower.json | 3 +- .../template/typeahead_criteria_item.html | 6 +- src/app/services/resource/task.js | 2 +- src/app/storyboard/module.js | 7 +- .../worklists/controller/add_controller.js | 44 ++++++ .../controller/additem_controller.js | 129 ++++++++++++++++ .../worklists/controller/delete_controller.js | 42 ++++++ .../worklists/controller/detail_controller.js | 140 ++++++++++++++++++ src/app/worklists/module.js | 41 +++++ src/app/worklists/template/additem.html | 89 +++++++++++ src/app/worklists/template/delete.html | 37 +++++ src/app/worklists/template/detail.html | 118 +++++++++++++++ src/index.html | 1 + src/theme/base/boards_worklists.less | 48 ++++++ src/theme/base/bootstrap/tables.less | 6 + src/theme/base/bootstrap_addons.less | 4 + src/theme/main.less | 6 + 18 files changed, 719 insertions(+), 7 deletions(-) create mode 100644 src/app/worklists/controller/add_controller.js create mode 100644 src/app/worklists/controller/additem_controller.js create mode 100644 src/app/worklists/controller/delete_controller.js create mode 100644 src/app/worklists/controller/detail_controller.js create mode 100644 src/app/worklists/module.js create mode 100644 src/app/worklists/template/additem.html create mode 100644 src/app/worklists/template/delete.html create mode 100644 src/app/worklists/template/detail.html create mode 100644 src/theme/base/boards_worklists.less diff --git a/Gruntfile.js b/Gruntfile.js index fc2f84e5..8f9505cf 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -150,7 +150,8 @@ module.exports = function (grunt) { dir.theme + '/storyboard/', dir.bower + '/bootstrap/less/', dir.bower + '/font-awesome/less/', - dir.bower + '/highlightjs/styles/' + dir.bower + '/highlightjs/styles/', + dir.bower + '/ng-sortable/dist/' ]; }, cleancss: true, diff --git a/bower.json b/bower.json index e5c4c7bd..8f4c5d2b 100644 --- a/bower.json +++ b/bower.json @@ -15,7 +15,8 @@ "angular-cache": "3.2.5", "angularjs-viewhead": "0.0.1", "marked": "0.3.4", - "highlightjs": "8.4" + "highlightjs": "8.4", + "ng-sortable": "1.3.1" }, "devDependencies": { "angular-mocks": "1.3.13", diff --git a/src/app/search/template/typeahead_criteria_item.html b/src/app/search/template/typeahead_criteria_item.html index fa013e49..4414c7a8 100644 --- a/src/app/search/template/typeahead_criteria_item.html +++ b/src/app/search/template/typeahead_criteria_item.html @@ -1,5 +1,6 @@ + class="header-criteria-item" + title="{{match.model.type}}: {{match.model.title}}">  {{match.model.title}} @@ -29,6 +30,9 @@  {{match.model.title}} + +  {{match.model.title}} +  {{match.model.type}} diff --git a/src/app/services/resource/task.js b/src/app/services/resource/task.js index 610be776..dc776bc8 100644 --- a/src/app/services/resource/task.js +++ b/src/app/services/resource/task.js @@ -33,7 +33,7 @@ angular.module('sb.services').factory('Task', ResourceFactory.applySearch( 'Task', resource, - null, + 'title', { Text: 'q', TaskStatus: 'status', diff --git a/src/app/storyboard/module.js b/src/app/storyboard/module.js index 9b16ba97..81858ab6 100644 --- a/src/app/storyboard/module.js +++ b/src/app/storyboard/module.js @@ -25,9 +25,10 @@ angular.module('storyboard', [ 'sb.services', 'sb.templates', 'sb.dashboard', 'sb.pages', 'sb.projects', 'sb.auth', 'sb.story', 'sb.profile', 'sb.notification', 'sb.search', - 'sb.admin', 'sb.subscription', 'sb.project_group', 'ui.router', - 'ui.bootstrap', 'monospaced.elastic', 'angularMoment', - 'angular-data.DSCacheFactory', 'viewhead', 'ngSanitize']) + 'sb.admin', 'sb.subscription', 'sb.project_group', 'sb.worklist', + 'ui.router', 'ui.bootstrap', 'monospaced.elastic', + 'angularMoment', 'angular-data.DSCacheFactory', 'viewhead', + 'ngSanitize', 'as.sortable']) .constant('angularMomentConfig', { preprocess: 'utc', timezone: 'UTC' diff --git a/src/app/worklists/controller/add_controller.js b/src/app/worklists/controller/add_controller.js new file mode 100644 index 00000000..a4bb1bde --- /dev/null +++ b/src/app/worklists/controller/add_controller.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2015 Codethink Limited + * + * 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 worklist" modal popup. + */ +angular.module('sb.worklist').controller('AddWorklistController', + function ($scope, $modalInstance, $state, params, Worklist) { + 'use strict'; + + /** + * Saves the worklist. + */ + $scope.save = function () { + $scope.worklist.$create( + function (result) { + $modalInstance.dismiss('success'); + $state.go('sb.worklist.detail', {worklistID: result.id}); + } + ); + }; + + /** + * Close this modal without saving. + */ + $scope.close = function () { + $modalInstance.dismiss('cancel'); + }; + + $scope.worklist = new Worklist({title: ''}); + }); diff --git a/src/app/worklists/controller/additem_controller.js b/src/app/worklists/controller/additem_controller.js new file mode 100644 index 00000000..42ef95d4 --- /dev/null +++ b/src/app/worklists/controller/additem_controller.js @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2015 Codethink Limited + * + * 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 "delete worklist" modal + */ +angular.module('sb.worklist').controller('WorklistAddItemController', + function ($log, $scope, $state, worklist, $modalInstance, Story, Task, + Criteria, Worklist, $q, valid) { + 'use strict'; + + $scope.worklist = worklist; + $scope.items = []; + + // Set our progress flags and clear previous error conditions. + $scope.loadingItems = false; + $scope.error = {}; + + $scope.save = function() { + var offset = $scope.worklist.items.length; + for (var i = 0; i < $scope.items.length; i++) { + var item = $scope.items[i]; + var item_type = ''; + + if (item.type === 'Task') { + item_type = 'task'; + } else if (item.type === 'Story') { + item_type = 'story'; + } + + var params = { + item_id: item.value, + id: $scope.worklist.id, + list_position: offset + i, + item_type: item_type + }; + + if (valid(item)) { + Worklist.ItemsController.create(params); + } + } + $modalInstance.dismiss('success'); + }; + + /** + * Remove an item from the list of items to add. + */ + $scope.removeItem = function (item) { + var idx = $scope.items.indexOf(item); + $scope.items.splice(idx, 1); + }; + + /** + * Item typeahead search method. + */ + $scope.searchItems = function (value) { + var deferred = $q.defer(); + + var searchString = value || ''; + + var searches = [ + Story.criteriaResolver(searchString, 5), + Task.criteriaResolver(searchString, 5) + ]; + + $q.all(searches).then(function (searchResults) { + var criteria = []; + + var addResult = function (item) { + if (valid(item)) { + criteria.push(item); + } + }; + + for (var i = 0; i < searchResults.length; i++) { + var results = searchResults[i]; + + if (!results) { + continue; + } + + if (!!results.forEach) { + results.forEach(addResult); + } else { + addResult(results); + } + } + + deferred.resolve(criteria); + }); + + return deferred.promise; + }; + + /** + * Formats the item name. + */ + $scope.formatItemName = function (model) { + if (!!model) { + return model.title; + } + return ''; + }; + + /** + * Select a new item. + */ + $scope.selectNewItem = function (model) { + $scope.items.push(model); + $scope.asyncItem = ''; + }; + + $scope.close = function () { + $modalInstance.dismiss('cancel'); + }; + }); diff --git a/src/app/worklists/controller/delete_controller.js b/src/app/worklists/controller/delete_controller.js new file mode 100644 index 00000000..29e61c9c --- /dev/null +++ b/src/app/worklists/controller/delete_controller.js @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2015 Codethink Limited + * + * 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 "delete worklist" modal + */ +angular.module('sb.worklist').controller('WorklistDeleteController', + function ($log, $scope, $state, worklist, $modalInstance) { + 'use strict'; + + $scope.worklist = worklist; + + // Set our progress flags and clear previous error conditions. + $scope.isUpdating = true; + $scope.error = {}; + + $scope.remove = function () { + $scope.worklist.$delete( + function () { + $modalInstance.dismiss('success'); + $state.go('sb.dashboard.worklist'); + } + ); + }; + + $scope.close = function () { + $modalInstance.dismiss('cancel'); + }; + }); diff --git a/src/app/worklists/controller/detail_controller.js b/src/app/worklists/controller/detail_controller.js new file mode 100644 index 00000000..899c869c --- /dev/null +++ b/src/app/worklists/controller/detail_controller.js @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2015 Codethink Limited + * + * 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. + */ + +/** + * A controller that manages the worklist detail page. + */ +angular.module('sb.worklist').controller('WorklistDetailController', + function ($scope, $modal, $timeout, $stateParams, Worklist) { + 'use strict'; + + /** + * Load the worklist and its contents. + */ + function loadWorklist() { + var params = {id: $stateParams.worklistID}; + Worklist.get(params).$promise.then(function(result) { + $scope.worklist = result; + Worklist.loadContents(result, true); + }); + } + + /** + * Save the worklist. + */ + function saveWorklist() { + $scope.worklist.$update().then(function() { + Worklist.loadContents($scope.worklist, true); + }); + } + + /** + * Toggle edit mode on the worklist. If going on->off then + * save changes. + */ + $scope.toggleEditMode = function() { + if (!$scope.worklist.editing) { + $scope.worklist.editing = true; + } else { + $scope.worklist.editing = false; + saveWorklist(); + } + }; + + /** + * Show a modal to handle adding items to the worklist. + */ + function showAddItemModal() { + var modalInstance = $modal.open({ + templateUrl: 'app/worklists/template/additem.html', + controller: 'WorklistAddItemController', + resolve: { + worklist: function() { + return $scope.worklist; + }, + valid: function() { + return function() { + // No limit on the contents of worklists + return true; + }; + } + } + }); + + return modalInstance.result; + } + + /** + * Display the add-item modal and reload the worklist when + * it is closed. + */ + $scope.addItem = function() { + showAddItemModal().finally(loadWorklist); + }; + + /** + * Remove an item from the worklist. + */ + $scope.removeItem = function(item) { + Worklist.ItemsController.delete({ + id: $scope.worklist.id, + item_id: item.list_item_id + }).$promise.then(function() { + var idx = $scope.worklist.items.indexOf(item); + $scope.worklist.items.splice(idx, 1); + }); + }; + + /** + * Show a modal to handle archiving the worklist. + */ + $scope.remove = function() { + var modalInstance = $modal.open({ + templateUrl: 'app/worklists/template/delete.html', + controller: 'WorklistDeleteController', + resolve: { + worklist: function() { + return $scope.worklist; + } + } + }); + return modalInstance.result; + }; + + /** + * Config for worklist sortable. + */ + $scope.sortableOptions = { + accept: function (sourceHandle, dest) { + return sourceHandle.itemScope.sortableScope.$id === dest.$id; + }, + orderChanged: function(result) { + var list = result.source.sortableScope.$parent.worklist; + for (var i = 0; i < list.items.length; i++) { + var item = list.items[i]; + item.position = i; + Worklist.ItemsController.update({ + id: list.id, + item_id: item.list_item_id, + list_position: item.position + }); + } + } + }; + + // Load the worklist. + loadWorklist(); + }); diff --git a/src/app/worklists/module.js b/src/app/worklists/module.js new file mode 100644 index 00000000..7bad5820 --- /dev/null +++ b/src/app/worklists/module.js @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2015 Codethink Limited + * + * 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.worklist', ['ui.router', 'sb.services', 'sb.util', + 'ui.bootstrap']) + .config(function ($stateProvider, $urlRouterProvider) { + 'use strict'; + + // URL Defaults. + $urlRouterProvider.when('/worklist', '/worklist/list'); + + // Set our page routes. + $stateProvider + .state('sb.worklist', { + abstract: true, + url: '/worklist', + template: '
' + }) + .state('sb.worklist.detail', { + url: '/{worklistID:[0-9]+}', + controller: 'WorklistDetailController', + templateUrl: 'app/worklists/template/detail.html' + }); + }); diff --git a/src/app/worklists/template/additem.html b/src/app/worklists/template/additem.html new file mode 100644 index 00000000..cc96da5b --- /dev/null +++ b/src/app/worklists/template/additem.html @@ -0,0 +1,89 @@ + +
+
+ +

Adding items to {{worklist.title}}

+
+
+
+
+
+
+ +
+ + +
+
+
+ + + + + + +
+ +  {{item.title}} + + +  {{item.title}} + + +
+
+
+
+
+ + +
+
+
+
diff --git a/src/app/worklists/template/delete.html b/src/app/worklists/template/delete.html new file mode 100644 index 00000000..4315a8c5 --- /dev/null +++ b/src/app/worklists/template/delete.html @@ -0,0 +1,37 @@ + +
+
+ +

{{worklist.title}}

+
+
+
diff --git a/src/app/worklists/template/detail.html b/src/app/worklists/template/detail.html new file mode 100644 index 00000000..0a6b0518 --- /dev/null +++ b/src/app/worklists/template/detail.html @@ -0,0 +1,118 @@ + +
+
+
+

+ {{worklist.title}} + + + + +

+
+
+

+ + + + + +

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ {{item.title}} +
+
+ +
+ +
+
+ {{item.title}} +
+
+
+ +
+ +
+
+ {{item.title}} +
+
+ +
+ +
+
+ {{item.title}} +
+
+
+ +
+
+ This worklist is currently empty. +
+ Add Items +
+
+
+
diff --git a/src/index.html b/src/index.html index 162fda92..ab64ecd6 100644 --- a/src/index.html +++ b/src/index.html @@ -41,6 +41,7 @@ + diff --git a/src/theme/base/boards_worklists.less b/src/theme/base/boards_worklists.less new file mode 100644 index 00000000..c3d8edd9 --- /dev/null +++ b/src/theme/base/boards_worklists.less @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2015 Codethink Limited + * + * 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. + */ + +/** + * Board and worklist styles. + */ + +.form-control-inline { + width: auto; + display: inline; +} + +/* Worklist style */ + +.hover-row { + width: 100vw; + &:hover { + cursor: pointer; + } +} + + +/* ng-sortable style */ + +.as-sortable-dragging > .as-sortable-item { + width: inherit; + height: inherit; +} + +.as-sortable-placeholder { + border: 1px dashed #ddd; + border-radius: 4px; + box-sizing: border-box; + background-color: #999; +} diff --git a/src/theme/base/bootstrap/tables.less b/src/theme/base/bootstrap/tables.less index eb48d4c8..ddb15c0f 100644 --- a/src/theme/base/bootstrap/tables.less +++ b/src/theme/base/bootstrap/tables.less @@ -42,3 +42,9 @@ table.table.table-clean { } } } + +.table-striped { + > tbody > tr:nth-of-type(even) { + background-color: @white; + } +} diff --git a/src/theme/base/bootstrap_addons.less b/src/theme/base/bootstrap_addons.less index c24c9a91..00f30219 100644 --- a/src/theme/base/bootstrap_addons.less +++ b/src/theme/base/bootstrap_addons.less @@ -207,3 +207,7 @@ td .form-group:last-child { div .container-fluid { margin: 0% 5%; } + +.modal-checkbox { + margin-left: 0px !important; +} diff --git a/src/theme/main.less b/src/theme/main.less index 1b353f56..522714dd 100644 --- a/src/theme/main.less +++ b/src/theme/main.less @@ -24,8 +24,13 @@ @import './bootstrap.less'; @import './base/bootstrap/navbar.less'; @import './font-awesome.less'; + +// HighlightJS theme @import (less) './default.css'; +// ng-sortable styles +@import (less) './ng-sortable.css'; + // Theme @import './theme.less'; // Addons to the bootstrap theme. @@ -43,3 +48,4 @@ @import './base/header.less'; @import './base/icons.less'; @import './base/edit_tasks.less'; +@import './base/boards_worklists.less';