Merge branch 'dev-angular' into feature/toolbar-group-by

Conflicts:
	app/assets/javascripts/angular/directives/work_packages/options-dropdown-directive.js
	app/assets/javascripts/angular/models/query.js
	app/views/work_packages/index.html.erb
pull/1318/head
Richard 11 years ago
commit 76b6860105
  1. 52
      app/assets/javascripts/angular/controllers/dialogs/sorting.js
  2. 47
      app/assets/javascripts/angular/controllers/work-package-details-controller.js
  3. 30
      app/assets/javascripts/angular/controllers/work-packages-controller.js
  4. 6
      app/assets/javascripts/angular/directives/work_packages/options-dropdown-directive.js
  5. 8
      app/assets/javascripts/angular/models/query.js
  6. 8
      app/assets/javascripts/angular/models/sortation.js
  7. 85
      app/assets/javascripts/angular/openproject-app.js
  8. 52
      app/assets/javascripts/angular/routing.js
  9. 4
      app/assets/javascripts/angular/services/project-service.js
  10. 8
      app/assets/javascripts/angular/services/query-service.js
  11. 83
      app/assets/javascripts/application.js.erb
  12. 80
      app/assets/stylesheets/content/_action_menu_main.md
  13. 103
      app/assets/stylesheets/content/_action_menu_main.sass
  14. 12
      app/assets/stylesheets/content/_headings.md
  15. 12
      app/assets/stylesheets/content/_headings.sass
  16. 16
      app/assets/stylesheets/content/_modal.md
  17. 75
      app/assets/stylesheets/content/_modal.sass
  18. 38
      app/assets/stylesheets/fonts/_openproject_icon_font.sass
  19. 34
      app/assets/stylesheets/global/_mixins.sass
  20. 9
      app/assets/stylesheets/global/_variables.sass
  21. 23
      app/assets/stylesheets/layout/_drop_down.sass
  22. 3
      app/controllers/api/v3/projects_controller.rb
  23. 2
      app/controllers/work_packages_controller.rb
  24. 143
      app/views/layouts/angular.html.erb
  25. 62
      app/views/work_packages/_list.html.erb
  26. 43
      app/views/work_packages/_query_form.html.erb
  27. 57
      app/views/work_packages/index.html.erb
  28. 3
      bower.json
  29. 6
      features/issues/query.feature
  30. 1
      karma.conf.js
  31. 56
      karma/tests/controllers/work-package-details-controller-test.js
  32. 22
      karma/tests/controllers/work-packages-controller-test.js
  33. 83
      karma/tests/services/project-service-test.js
  34. 68
      public/templates/work_packages.html
  35. 12
      public/templates/work_packages.list.details.html
  36. 31
      public/templates/work_packages.list.html
  37. 13
      public/templates/work_packages/column_context_menu.html
  38. 8
      public/templates/work_packages/modals/columns.html
  39. 4
      public/templates/work_packages/modals/export.html
  40. 10
      public/templates/work_packages/modals/save.html
  41. 4
      public/templates/work_packages/modals/settings.html
  42. 6
      public/templates/work_packages/modals/share.html
  43. 22
      public/templates/work_packages/modals/sorting.html
  44. 3
      public/templates/work_packages/work_package_context_menu.html
  45. 16
      public/templates/work_packages/work_packages_table.html
  46. 4
      spec/features/accessibility/custom_fields_spec.rb
  47. 14
      spec/features/accessibility/work_packages/work_package_query_spec.rb

@ -36,7 +36,57 @@ angular.module('openproject.workPackages.controllers')
});
}])
.controller('SortingModalController', ['sortingModal', function(sortingModal) {
.controller('SortingModalController', ['sortingModal',
'$scope',
'QueryService',
function(sortingModal, $scope, QueryService) {
this.name = 'Sorting';
this.closeMe = sortingModal.deactivate;
$scope.sortByOptions = {};
$scope.initSortation = function(){
var currentSortation = QueryService.getSortation();
$scope.sortElements = currentSortation.sortElements.map(function(element){
return [$scope.availableColumnsData.filter(function(column) { return column.id == element.field; })[0],
$scope.availableDirectionsData.filter(function(direction) { return direction.id == element.direction; })[0]]
});
while($scope.sortElements.length < 3) {
$scope.sortElements.push([]);
}
}
$scope.getAvailableColumnsData = function(term, result) {
result($scope.availableColumnsData);
}
$scope.getDirectionsData = function(term, result) {
result([{ id: 'asc', label: 'Ascending'}, { id: 'desc', label: 'Descending'}]);
}
$scope.updateSortation = function(){
var sortElements = $scope.sortElements
.filter(function(element){
return element.length == 2;
})
.map(function(element){
return { field: element[0].id, direction: element[1].id }
})
QueryService.updateSortElements(sortElements);
sortingModal.deactivate();
}
QueryService.loadAvailableColumns()
.then(function(available_columns){
$scope.availableColumns = available_columns
$scope.availableColumnsData = available_columns.map(function(column){
return { id: column.name, label: column.title, other: column.title };
});
$scope.initSortation();
});
$scope.availableDirectionsData = [{ id: 'desc', label: 'Descending'}, { id: 'asc', label: 'Ascending'}];
}]);

@ -0,0 +1,47 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
angular.module('openproject.workPackages.controllers')
.controller('WorkPackageDetailsController', [
'$scope',
'$stateParams',
function($scope, $stateParams) {
$scope.workPackageId = $stateParams.workPackageId
$scope.$watch('rows', function(rows) {
if (rows && rows.length > 0) {
var row = $scope.rows.find(function(row) {
return row.object.id == $scope.workPackageId;
});
$scope.workPackage = row ? row.object : {};
}
});
}
]);

@ -33,6 +33,7 @@ angular.module('openproject.workPackages.controllers')
'$q',
'$window',
'$location',
'ProjectService',
'WorkPackagesTableService',
'WorkPackageService',
'QueryService',
@ -40,12 +41,12 @@ angular.module('openproject.workPackages.controllers')
'WorkPackageLoadingHelper',
'INITIALLY_SELECTED_COLUMNS',
'OPERATORS_AND_LABELS_BY_FILTER_TYPE',
function($scope, $q, $window, $location, WorkPackagesTableService,
function($scope, $q, $window, $location, ProjectService,
WorkPackagesTableService,
WorkPackageService, QueryService, PaginationService,
WorkPackageLoadingHelper, INITIALLY_SELECTED_COLUMNS,
OPERATORS_AND_LABELS_BY_FILTER_TYPE) {
$scope.projectTypes = $window.gon.project_types;
$scope.showFiltersOptions = false;
@ -53,6 +54,7 @@ angular.module('openproject.workPackages.controllers')
function initialSetup() {
setUrlParams($window.location);
initProject();
$scope.selectedTitle = "Work Packages";
$scope.operatorsAndLabelsByFilterType = OPERATORS_AND_LABELS_BY_FILTER_TYPE;
@ -81,6 +83,30 @@ angular.module('openproject.workPackages.controllers')
if(match) $scope.query_id = match[1];
}
function initProject() {
if ($scope.projectIdentifier) {
ProjectService.getProject($scope.projectIdentifier).then(function(project) {
$scope.project = project;
$scope.projects = [ $scope.project ];
$scope.availableTypes = $scope.project.embedded.types;
});
} else {
ProjectService.getProjects().then(function(projects) {
var allTypes, availableTypes;
$scope.projects = projects;
allTypes = projects.map(function(project) {
return project.embedded.types;
}).reduce(function(a, b) {
return a.concat(b);
}, []);
$scope.availableTypes = allTypes; // TODO remove duplicates
});
}
}
function setupPage(json) {
initQuery(json.meta);
setupWorkPackagesTable(json);

@ -53,7 +53,6 @@ angular.module('openproject.workPackages.directives')
scope.showExportModal = exportModal.activate;
scope.showSettingsModal = settingsModal.activate;
scope.showShareModal = shareModal.activate;
scope.showSortingModal = sortingModal.activate;
scope.showGroupingModal = groupingModal.activate;
scope.showSaveModal = function(saveAs){
@ -73,6 +72,11 @@ angular.module('openproject.workPackages.directives')
columnsModal.activate();
};
scope.showSortingModal = function(){
scope.$emit('hideAllDropdowns');
sortingModal.activate();
};
scope.toggleDisplaySums = function(){
scope.$emit('hideAllDropdowns');
scope.query.displaySums = !scope.query.displaySums;

@ -92,6 +92,10 @@ angular.module('openproject.models')
return UrlParamsHelper.buildQueryString(this.toParams());
},
getSortation: function(){
return this.sortation;
},
setSortation: function(sortation){
this.sortation = sortation;
},
@ -100,6 +104,10 @@ angular.module('openproject.models')
this.groupBy = groupBy;
},
updateSortElements: function(sortElements){
this.sortation.setSortElements(sortElements);
},
setName: function(name) {
this.name = name;
},

@ -83,6 +83,14 @@ angular.module('openproject.models')
this.sortElements.unshift(sortElement);
};
Sortation.prototype.setSortElements = function(sortElements) {
var elements = this.sortElements;
elements.length = 0;
angular.forEach(sortElements, function(element){
elements.push(element);
});
};
Sortation.prototype.getTargetSortationOfHeader = function(headerName) {
var targetSortation = angular.copy(this);
var targetSortDirection = this.getCurrentSortDirectionOfHeader(headerName) === 'asc' ? 'desc' : 'asc';

@ -27,37 +27,94 @@
//++
// global
angular.module('openproject.services', ['openproject.uiComponents', 'openproject.helpers', 'openproject.workPackages.config', 'openproject.workPackages.helpers']);
angular.module('openproject.services', [
'openproject.uiComponents',
'openproject.helpers',
'openproject.workPackages.config',
'openproject.workPackages.helpers'
]);
angular.module('openproject.helpers', ['openproject.services']);
angular.module('openproject.models', ['openproject.workPackages.config', 'openproject.services']);
angular.module('openproject.models', [
'openproject.workPackages.config',
'openproject.services'
]);
// timelines
angular.module('openproject.timelines', ['openproject.timelines.controllers', 'openproject.timelines.directives', 'openproject.uiComponents']);
angular.module('openproject.timelines', [
'openproject.timelines.controllers',
'openproject.timelines.directives',
'openproject.uiComponents'
]);
angular.module('openproject.timelines.models', ['openproject.helpers']);
angular.module('openproject.timelines.helpers', []);
angular.module('openproject.timelines.controllers', ['openproject.timelines.models']);
angular.module('openproject.timelines.services', ['openproject.timelines.models', 'openproject.timelines.helpers']);
angular.module('openproject.timelines.directives', ['openproject.timelines.models', 'openproject.timelines.services', 'openproject.uiComponents', 'openproject.helpers']);
angular.module('openproject.timelines.controllers', [
'openproject.timelines.models'
]);
angular.module('openproject.timelines.services', [
'openproject.timelines.models',
'openproject.timelines.helpers'
]);
angular.module('openproject.timelines.directives', [
'openproject.timelines.models',
'openproject.timelines.services',
'openproject.uiComponents',
'openproject.helpers'
]);
// work packages
angular.module('openproject.workPackages', ['openproject.workPackages.controllers', 'openproject.workPackages.filters', 'openproject.workPackages.directives', 'openproject.uiComponents']);
angular.module('openproject.workPackages', [
'openproject.workPackages.controllers',
'openproject.workPackages.filters',
'openproject.workPackages.directives',
'openproject.uiComponents'
]);
angular.module('openproject.workPackages.services', []);
angular.module('openproject.workPackages.helpers', ['openproject.helpers', 'openproject.workPackages.services']);
angular.module('openproject.workPackages.filters', ['openproject.workPackages.helpers']);
angular.module('openproject.workPackages.helpers', [
'openproject.helpers',
'openproject.workPackages.services'
]);
angular.module('openproject.workPackages.filters', [
'openproject.workPackages.helpers'
]);
angular.module('openproject.workPackages.config', []);
angular.module('openproject.workPackages.controllers', ['openproject.models', 'openproject.workPackages.helpers', 'openproject.services', 'openproject.workPackages.config', 'btford.modal']);
angular.module('openproject.workPackages.directives', ['openproject.uiComponents', 'openproject.services', 'openproject.workPackages.services', 'ng-context-menu']);
angular.module('openproject.workPackages.controllers', [
'openproject.models',
'openproject.workPackages.helpers',
'openproject.services',
'openproject.workPackages.config',
'btford.modal'
]);
angular.module('openproject.workPackages.directives', [
'openproject.uiComponents',
'openproject.services',
'openproject.workPackages.services',
'ng-context-menu'
]);
// messages
angular.module('openproject.messages', ['openproject.messages.controllers']);
angular.module('openproject.messages.controllers', []);
// time entries
angular.module('openproject.timeEntries', ['openproject.timeEntries.controllers']);
angular.module('openproject.timeEntries', [
'openproject.timeEntries.controllers'
]);
angular.module('openproject.timeEntries.controllers', []);
// main app
var openprojectApp = angular.module('openproject', ['ui.select2', 'ui.select2.sortable', 'ui.date', 'openproject.uiComponents', 'openproject.timelines', 'openproject.workPackages', 'openproject.messages', 'openproject.timeEntries', 'ngAnimate', 'ngSanitize']);
var openprojectApp = angular.module('openproject', [
'ui.select2',
'ui.select2.sortable',
'ui.date',
'ui.router',
'openproject.uiComponents',
'openproject.timelines',
'openproject.workPackages',
'openproject.messages',
'openproject.timeEntries',
'ngAnimate',
'ngSanitize'
]);
window.appBasePath = jQuery('meta[name=app_base_path]').attr('content') || '';
@ -73,7 +130,7 @@ openprojectApp
config.url = window.appBasePath + config.url;
return config || $q.when(config);
}
}
};
});
}])
.run(['$http', function($http){

@ -0,0 +1,52 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
openprojectApp.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/wp");
$stateProvider
.state('work-packages', {
url: "/wp",
abstract: true,
templateUrl: "/templates/work_packages.html",
controller: 'WorkPackagesController'
})
.state('work-packages.list', {
url: "",
templateUrl: "/templates/work_packages.list.html"
})
.state('work-packages.list.details', {
url: "/:workPackageId",
templateUrl: "/templates/work_packages.list.details.html",
controller: 'WorkPackageDetailsController'
})
}]);

@ -6,7 +6,9 @@ angular.module('openproject.services')
getProject: function(projectIdentifier) {
var url = PathHelper.apiV3ProjectPath(projectIdentifier);
return ProjectService.doQuery(url);
return $http.get(url).then(function(response) {
return response.data.project;
});
},
getProjects: function() {

@ -165,6 +165,14 @@ angular.module('openproject.services')
this.showColumns(selectedColumnNames);
},
updateSortElements: function(sortation) {
return query.updateSortElements(sortation);
},
getSortation: function() {
return query.getSortation();
},
getAvailableFilters: function(projectIdentifier){
// TODO once this is becoming more single-page-app-like keep the available filters of the query model in sync when the project identifier is changed on the scope but the page isn't reloaded
var identifier = 'global';

@ -56,6 +56,7 @@
//= require angular
//= require angular-animate
//= require angular-modal
//= require angular-ui-router
//= require angular-ui-select2
//= require angular-ui-select2-sortable
//= require angular-ui-date/src/date
@ -144,19 +145,19 @@ jQuery(document).ready(function ($) {
});
function checkAll (id, checked) {
var els = Element.descendants(id);
for (var i = 0; i < els.length; i++) {
var els = Element.descendants(id);
for (var i = 0; i < els.length; i++) {
if (els[i].disabled==false) {
els[i].checked = checked;
}
}
}
}
function toggleCheckboxesBySelector(selector) {
boxes = $$(selector);
var all_checked = true;
for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; }
boxes = $$(selector);
var all_checked = true;
for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; }
}
function setCheckboxesBySelector(checked, selector) {
@ -167,9 +168,9 @@ function setCheckboxesBySelector(checked, selector) {
}
function showAndScrollTo(id, focus) {
Element.show(id);
if (focus!=null) { Form.Element.focus(focus); }
Element.scrollTo(id);
Element.show(id);
if (focus!=null) { Form.Element.focus(focus); }
Element.scrollTo(id);
}
// TODO de-implement once table component has been used
@ -217,9 +218,9 @@ function toggleAllRowGroups(el) {
}
function hideFieldset(el) {
var fieldset = Element.up(el, 'fieldset');
fieldset.toggleClassName('collapsed');
fieldset.down('>div').hide();
var fieldset = Element.up(el, 'fieldset');
fieldset.toggleClassName('collapsed');
fieldset.down('>div').hide();
}
var fileFieldCount = 1;
@ -252,10 +253,10 @@ function promptToRemote(text, param, url) {
function collapseScmEntry(id) {
var els = document.getElementsByClassName(id, 'browser');
for (var i = 0; i < els.length; i++) {
if (els[i].hasClassName('open')) {
collapseScmEntry(els[i].id);
}
for (var i = 0; i < els.length; i++) {
if (els[i].hasClassName('open')) {
collapseScmEntry(els[i].id);
}
Element.hide(els[i]);
}
$(id).removeClassName('open');
@ -263,7 +264,7 @@ function collapseScmEntry(id) {
function expandScmEntry(id) {
var els = document.getElementsByClassName(id, 'browser');
for (var i = 0; i < els.length; i++) {
for (var i = 0; i < els.length; i++) {
Element.show(els[i]);
if (els[i].hasClassName('loaded') && !els[i].hasClassName('collapsed')) {
expandScmEntry(els[i].id);
@ -297,12 +298,12 @@ function scmEntryLoaded(id) {
}
function randomKey(size) {
var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
var key = '';
for (i = 0; i < size; i++) {
key += chars[Math.floor(Math.random() * chars.length)];
}
return key;
var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
var key = '';
for (i = 0; i < size; i++) {
key += chars[Math.floor(Math.random() * chars.length)];
}
return key;
}
// Automatic project identifier generation
@ -682,27 +683,27 @@ jQuery(document).ready(function($) {
});
});
// file table thumbnails
$("table a.has-thumb").hover(function() {
$(this).removeAttr("title").toggleClass("active");
// file table thumbnails
$("table a.has-thumb").hover(function() {
$(this).removeAttr("title").toggleClass("active");
// grab the image dimensions to position it properly
var thumbImg = $(this).find("img");
var thumbImgLeft = -(thumbImg.outerWidth() );
var thumbImgTop = -(thumbImg.height() / 2 );
thumbImg.css({top: thumbImgTop, left: thumbImgLeft}).show();
// grab the image dimensions to position it properly
var thumbImg = $(this).find("img");
var thumbImgLeft = -(thumbImg.outerWidth() );
var thumbImgTop = -(thumbImg.height() / 2 );
thumbImg.css({top: thumbImgTop, left: thumbImgLeft}).show();
}, function() {
$(this).toggleClass("active").find("img").hide();
});
}, function() {
$(this).toggleClass("active").find("img").hide();
});
// show/hide the files table
$(".attachments h4").click(function() {
$(this).toggleClass("closed").next().slideToggle(animationRate);
});
// show/hide the files table
$(".attachments h4").click(function() {
$(this).toggleClass("closed").next().slideToggle(animationRate);
});
// deal with potentially problematic super-long titles
$(".title-bar h2").css({paddingRight: $(".title-bar-actions").outerWidth() + 15 });
// deal with potentially problematic super-long titles
$(".title-bar h2").css({paddingRight: $(".title-bar-actions").outerWidth() + 15 });
$(window).resize(function() {
// wait 200 milliseconds for no further resize event

@ -2,50 +2,42 @@
```
<div class="action-menu">
<ul class="menu">
<li>
<a href="#"><i class="icon-edit icon-actionmenu"></i>menu item for modal...</a>
</li>
<li>
<a href="#"><i class="icon-yes icon-actionmenu"></i>menu item</a>
</li>
<li>
<a href="#"><i class="icon-copy icon-actionmenu"></i>menu item</a>
</li>
<li class="submenu-item">
<a href="#"><i class="icon-priority icon-actionmenu"></i>menu item with sub</a><i class="icon-pulldown-arrow4 icon-submenu"></i>
</li>
<li>
<a href="#"><i class="icon-delete icon-actionmenu"></i>menu item</a>
</li>
<li class="hasnoicon">
<a href="#">menu item no icon</a>
</li>
<li class="dropdown-divider"></li>
<li>
<a href="#"><i class="icon-time icon-actionmenu"></i>menu item</a>
</li>
</ul>
<ul class="menu">
<li>
<a href="#"><i class="icon-edit icon-action-menu"></i>menu item for modal...</a>
</li>
<li>
<a href="#"><i class="icon-yes icon-action-menu"></i>menu item</a>
</li>
<li>
<a href="#"><i class="icon-copy icon-action-menu"></i>menu item</a>
</li>
<li>
<a href="#"><i class="icon-priority icon-action-menu"></i>menu item with sub</a>
<i class="icon-pulldown-arrow4 icon-sub-menu"></i>
<ul class="sub-menu">
<li>
<a href="#"><i class="icon-edit icon-action-menu"></i>menu item</a>
</li>
<li>
<a href="#"><i class="icon-yes icon-action-menu"></i>menu item</a>
</li>
<li>
<a href="#"><i class="icon-copy icon-action-menu"></i>menu item</a>
</li>
</ul>
</li>
<li>
<a href="#"><i class="icon-delete icon-action-menu"></i>menu item</a>
</li>
<li class="has-no-icon">
<a href="#">menu item no icon</a>
</li>
<li class="dropdown-divider"></li>
<li>
<a href="#"><i class="icon-time icon-action-menu"></i>menu item</a>
</li>
</ul>
</div>
<div id="submenu" class="action-menu">
<ul class="menu">
<li>
<a href="#"><i class="icon-edit icon-actionmenu"></i>menu item for modal...</a>
</li>
<li>
<a href="#"><i class="icon-yes icon-actionmenu"></i>menu item</a>
</li>
<li>
<a href="#"><i class="icon-copy icon-actionmenu"></i>menu item</a>
</li>
</ul>
</div>
```

@ -26,105 +26,44 @@
* See doc/COPYRIGHT.rdoc for more details. ++
*/
@mixin action_menu_defaults($margin-top: 7px)
float: right
margin-top: $margin-top
> li
float: left
position: relative
list-style: none
@mixin contextual($margin-top: 8px)
float: right
white-space: nowrap
line-height: 1.4em
margin-top: $margin-top
padding-left: 10px
ul.action_menu_main
@include action_menu_defaults
ul.action_menu_specific,
.nosidebar ul.action_menu_specific
@include action_menu_defaults(-34px)
p.subtitle + ul.action_menu_specific
@include action_menu_defaults(-57px)
ul.action_menu_more
position: absolute
top: 23px
right: 0px
z-index: 100
white-space: nowrap
padding: 10px
padding-top: 5px
background: white
border: 1px solid #B7B7B7
box-shadow: 1px 1px 2px #aaa
> li
padding-top: 5px
#lower-title-bar ul.action_menu_specific
@include action_menu_defaults
padding-top: 10px
#lower-title-bar ul.action_menu_more
bottom: 0
right: 0
margin-bottom: 25px
top: auto
> li.drop-down
position: relative
.message-reply-menu
@include contextual(-39px)
.action-menu
float: left
width: 200px
border: 1px solid #dddddd
box-shadow: 1px 1px 4px #cccccc
-webkit-box-shadow: 1px 1px 4px #cccccc
padding: 3px 0
background: $action_menu_bg_color
ul
list-style-type: none
padding: 0
margin: 0
width: 200px
border: 1px solid #dddddd
box-shadow: 1px 1px 4px #cccccc
padding: 3px 0
background: #ffffff
li
padding: 4px 10px
padding: 4px 13px 4px 10px
&:hover
background: #f0f0f0
&.hasnoicon
&.has-no-icon
padding: 4px 10px 4px 35px
&.dropdown-divider
border-top: 1px solid #eeeeee
margin: 3px 0
padding: 0
font-size: 1px
&:hover ul
display: block
margin: -28px 0 0 190px
a
color: $main_menu_font_color
font-weight: normal
white-space: nowrap
&:hover
text-decoration: none
ul
display: none
position: absolute
i
.icon-actionmenu
padding: 0 10px 0 0
font-size: 15px
line-height: 5px
vertical-align: -40%
.icon-submenu
padding: 0 0 0 0
float: right
font-size: 15px
line-height: 5px
vertical-align: -40%
.icon-action-menu
@include icon-action-menu-rules
.icon-sub-menu
@include icon-sub-menu-rules
#submenu
margin: 81px 0 0 0

@ -1 +1,13 @@
# Headings
```
<h1>Headline H1</h1>
<span class="sample-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.<br /><br /></span>
<h2>Headline H2</h2>
<span class="sample-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.<br /><br /></span>
<h3>Headline H3</h3>
<span class="sample-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.<br /><br /></span>
<h4>Headline H4</h4>
<span class="sample-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.<br /><br /></span>
```

@ -29,3 +29,15 @@
#content
h2
padding-right: 340px
h1
@include default-headline-h1
h2
@include default-headline-h2
h3
@include default-headline-h3
h4
@include default-headline-h4

@ -1 +1,17 @@
# Modal
```
<div id="content-behind-modal">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Crasdapibus. Vivamus elementum semper</div>
<div id="modal-dark-overlay">
</div>
<div class="modal-container">
<div class="modal-header"><i class="icon-close"></i></div>
<div class="modal-content">
<h3>Modal Headline</h3>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper
</div>
</div>
```

@ -12,7 +12,7 @@ $ng-modal-image-width: $ng-modal-image-height
.ng-modal-window
// overlay
+position(fixed, 0px 0px 0px 0px)
background: rgba(0, 0, 0, 0.2)
background: rgba(0, 0, 0, 0.5)
text-align: left
z-index: 10000
@ -32,30 +32,8 @@ $ng-modal-image-width: $ng-modal-image-height
.ng-modal-inner
top: 0
.ng-modal-close
+position(absolute, $ng-modal-padding / 2 $ng-modal-padding / 2 0 0)
+size(1.5em)
cursor: pointer
background: $ng-modal-background
&:after,
&:before
+position(absolute, 3px 3px 0 50%)
+transform(rotate(45deg))
+size(0.15em 1.5em)
background: $ng-modal-close-color
content: ''
display: block
margin: -3px 0 0 -1px
&:hover:after,
&:hover:before
background: darken($ng-modal-close-color, 10)
&:before
+transform(rotate(-45deg))
.ng-modal-inner
+transition(opacity 0.25s ease)
border-radius: $base-border-radius
box-shadow: 0 5px 10px rgba(0, 0, 0, .2)
background: $ng-modal-background
margin: auto
max-height: 95%
@ -66,37 +44,32 @@ $ng-modal-image-width: $ng-modal-image-height
margin-top: .6em
//+media($medium-screen)
//padding: $ng-modal-padding
//width: 60%
//max-height: 60%
//width: 50%
//max-height: 50%
//margin-top: 10em
//+media($large-screen)
width: 50%
width: 40%
margin-top: 10em
h1
color: $base-font-color
margin-bottom: .6em
text-align: left
text-transform: capitalize
p
font-size: $base-font-size
max-width: 100% !important
.modal-header
padding: 0
text-align: left
&.intro
color: $blue
line-height: 1.6em
&.body
color: $base-font-color
line-height: 1.45em
//+media($medium-screen)
+columns(2 8em)
a.cta
color: white
display: inline-block
margin-right: .5em
margin-top: 1em
&:last-child
padding: 0 2em
i
float: right
cursor: pointer
label
padding: 0 50px 0 0
input
width: 230px
height: 30px
line-height: 30px
padding: 0 5px
background: #ffffff
border: 1px solid #cacaca
font-size: 13px
color: #222222
border-radius: 2px
button
margin: 30px 7px 0 0

@ -67,12 +67,16 @@
font-size: 12px
@mixin icon-dropdown-rules
padding: 0 0px 0 3px
padding: 0 0 0 3px
font-size: 13px
@mixin icon-button-rules
padding: 0 5px 0 0px
padding: 0 5px 0 0
font-size: 13px
@mixin icon-dropdown-menu-rules
padding: 0 8px 0 0
font-size: 14px
@mixin icon-context-rules
padding: 0 4px 0 0
@ -80,6 +84,19 @@
@mixin icon-table-rules
padding: 0 0 0 0
@mixin icon-action-menu-rules
padding: 0 10px 0 0
font-size: 15px
line-height: 5px
vertical-align: -40%
@mixin icon-sub-menu-rules
padding: 0 0 0 0
float: right
font-size: 15px
line-height: 5px
vertical-align: -40%
[data-icon]:before
@include icon-common
@ -111,6 +128,11 @@
content: attr(data-icon-dropdown)
@include icon-dropdown-rules
[data-icon-dropdown-menu]:before
@include icon-common
content: attr(data-icon-dropdown-menu)
@include icon-dropdown-menu-rules
[data-icon-button]:before
@include icon-common
content: attr(data-icon-button)
@ -121,6 +143,14 @@
content: attr(data-icon-table)
@include icon-table-rules
[data-icon-action-menu]:before
content: attr(data-icon-action-menu)
@include icon-action-menu-rules
[data-icon-sub-menu]:before
content: attr(data-icon-sub-menu)
@include icon-sub-menu-rules
[class^="icon-"]:before,
[class*=" icon-"]:before
@include icon-common
@ -152,6 +182,10 @@
.icon-dropdown:before
@include icon-dropdown-rules
// used for icons dropdown-menus
.icon-dropdown-menu:before
@include icon-dropdown-menu-rules
// used for icons in buttons
.icon-buttons:before
@include icon-button-rules

@ -37,6 +37,40 @@ $button_gray_font_color: #222222
@each $vendor in $vendors
#{$vendor}transition: all 200ms ease-in-out 0s
@mixin default-headline-h1
color: $headline_h1_font-color
font-size: $headline_h1_font_size
font-family: $font_family_normal
font-weight: normal
padding: 0 0 8px 0
margin: 0
@mixin default-headline-h2
color: $headline_h2_font-color
font-size: $headline_h2_font_size
font-family: $font_family_normal
font-weight: normal
text-transform: uppercase
padding: 0 0 8px 0
margin: 0
@mixin default-headline-h3
color: $headline_h3_font-color
font-size: $headline_h3_font_size
font-family: $font_family_normal
font-weight: normal
border-bottom: 1px solid #dddddd
padding: 0 0 8px 0
margin: 0 0 20px 0
@mixin default-headline-h4
color: $headline_h4_font-color
font-size: $headline_h4_font_size
font-family: $font_family_normal
font-weight: normal
padding: 0 0 5px 0
margin: 0 0 20px 0
@mixin default-font-normal($color, $font-size: 13px)
color: $color
font-size: $font-size

@ -30,6 +30,15 @@ $global_font_color: #555555 !default
$global_font_size: 13px !default
$global_line_height: 1.5 !default
$headline_h1_font_size: 28px !default
$headline_h1_font-color: #555555 !default
$headline_h2_font_size: 22px !default
$headline_h2_font-color: #06799F !default
$headline_h3_font_size: 19px !default
$headline_h3_font-color: #555555 !default
$headline_h4_font_size: 17px !default
$headline_h4_font-color: #555555 !default
$header_height: 55px !default
$header_bg_color: #3493B3 !default
$header_border_bottom_color: #3493B3 !default

@ -32,6 +32,10 @@
// https://github.com/plapier/jquery-dropdown
// (dual MIT/GPL-Licensed)
#settingsDropdown
margin: 10px 0 0 0
.dropdown
position: absolute
z-index: 9999999
@ -45,7 +49,7 @@
background: #FFF
border: solid 1px #DDD
border: solid 1px rgba(0, 0, 0, .2)
border-radius: 6px
border-radius: 0px
box-shadow: 0 5px 10px rgba(0, 0, 0, .2)
overflow: visible
padding: 4px 0
@ -103,21 +107,28 @@
color: #555
text-decoration: none
line-height: 18px
padding: 3px 15px
padding: 3px 32px
white-space: nowrap
.dropdown .dropdown-menu LI > A:hover,
.dropdown .dropdown-menu LABEL:hover
background-color: #08C
color: #FFF
background-color: #F0F0F0
cursor: pointer
.dropdown LI > A.dropdown-menu-hasicons
display: block
color: #555
text-decoration: none
line-height: 18px
padding: 3px 10px
white-space: nowrap
.dropdown .dropdown-menu .dropdown-divider
font-size: 1px
border-top: solid 1px #E5E5E5
padding: 0
margin: 5px 0
margin: 4px 0
/* Icon Examples - icons courtesy of http://p.yusukekamiyamane.com/ */
.dropdown.has-icons LI > A

@ -64,7 +64,8 @@ module Api
private
def find_project
@project = Project.find(params[:project_id])
@project = Project.where(identifier: params[:project_id]).first ||
Project.find(params[:id])
end
end

@ -206,7 +206,7 @@ class WorkPackagesController < ApplicationController
format.html do
render :index, :locals => { :query => @query,
:project => @project },
:layout => !request.xhr?
:layout => 'angular' # !request.xhr?
end
format.csv do
serialized_work_packages = WorkPackage::Exporter.csv(@work_packages, @project)

@ -0,0 +1,143 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%><!DOCTYPE html>
<% show_decoration = params["layout"].nil? %>
<html xmlns="http://www.w3.org/1999/xhtml" lang="<%= I18n.locale.to_s %>" xml:lang="<%= I18n.locale.to_s %>" class="<%= 'in_modal' unless show_decoration %>">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title><%= html_title %></title>
<meta name="description" content="<%= OpenProject::Info.app_name %>" />
<meta name="keywords" content="issue,bug,type" />
<meta name="app_base_path" content="<%= OpenProject::Configuration['rails_relative_url_root'] || '' %>" />
<meta name="current_menu_item" content="<%= current_menu_item %>" />
<meta name="accessibility-mode" content="<%= current_user.impaired? %>" />
<%= csrf_meta_tags %>
<%= favicon_link_tag 'favicon.ico' %>
<%= stylesheet_link_tag current_theme.stylesheet_manifest, :media => "all" %>
<% if User.current.impaired? && accessibility_css_enabled? %>
<%= stylesheet_link_tag 'accessibility' %>
<% end %>
<%= javascript_include_tag 'application' %>
<%= javascript_include_tag 'angular-i18n/angular-locale_de-de' if I18n.locale == :de %>
<!-- user specific tags -->
<%= user_specific_javascript_includes %>
<!-- project specific tags -->
<%= call_hook :view_layouts_base_html_head %>
<!-- page specific tags -->
<%= content_for(:header_tags) if content_for?(:header_tags) %>
</head>
<body class="<%= body_css_classes %>" ng-app="openproject">
<noscript>
<div class="top-shelf icon icon-warning">
<h1><%=l(:noscript_heading)%></h1>
<p>
<%=l(:noscript_description)%>
</p>
<h2>
<a href="http://www.enable-javascript.com/" target="_blank"><%=l(:noscript_learn_more) %></a>
</h2>
</div>
</noscript>
<% main_menu = render_main_menu(@project) %>
<% side_displayed = content_for?(:sidebar) || content_for?(:main_menu) || !main_menu.blank? %>
<div id="wrapper" class="<%= (side_displayed) ? '' : "nosidebar" %><%= (show_decoration) ? '' : 'nomenus' %>">
<% if show_decoration %>
<div id="top-menu">
<div id="header">
<div id="logo">
<%= link_to(I18n.t('label_home'), home_url, :class => 'home-link') %>
</div>
<div id="top-menu-items">
<%= render_top_menu_left %>
<div class="top-menu-items-right">
<%= render :partial => 'search/mini_form' %>
<h1 class="hidden-for-sighted">
<%= l(:label_top_menu) %>
</h1>
<%= render_top_menu_right %>
</div>
</div>
</div>
</div>
<% end %>
<div id="main" class="<%= side_displayed ? '' : "nosidebar" %><%= (show_decoration) ? '' : 'nomenus' %>">
<% if (side_displayed && show_decoration) %>
<div id="main-menu">
<h1 class="hidden-for-sighted"><%= l(:label_main_menu) %></h1>
<div id="toggle-project-menu">
<a href="javascript:;" title="<%= l(:show_hide_project_menu) %>" class="navigation-toggler icon4 icon-double-arrow-left"></a>
</div>
<div id="menu-sidebar">
<%= main_menu %>
<%= content_for :main_menu %>
<!-- Sidebar -->
<div id="sidebar">
<%= content_for :sidebar %>
<%= call_hook :view_layouts_base_sidebar %>
</div>
</div>
</div>
<% end %>
<% if show_decoration %>
<div id="breadcrumb" class="<%= (side_displayed) ? '' : "nosidebar" %><%= (show_decoration) ? '' : 'nomenus' %>">
<h1>
<%= you_are_here_info %>
<%= full_breadcrumb %>
</h1>
</div>
<% end %>
<div class="<%= (side_displayed) ? '' : "nosidebar" %><%= (show_decoration) ? '' : 'nomenus' %>" id="content">
<div ui-view></div>
<%= call_hook :view_layouts_base_content %>
<div style="clear:both;">&nbsp;</div>
</div>
</div>
<% if (show_decoration) %>
<div id="footer">
<div class="footer-content">
<%= footer_content %>
</div>
</div>
<% end %>
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
</div>
<%= call_hook :view_layouts_base_body_bottom %>
</body>
</html>

@ -1,62 +0,0 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= form_tag({}) do -%>
<back-url></back-url>
<div class="autoscroll">
<work-packages-table ng-if="rows && columns"
project-identifier="projectIdentifier"
columns="columns"
rows="rows"
query="query"
group-by="query.groupBy"
group-by-column="groupByColumn"
count-by-group="workPackageCountByGroup"
display-sums="query.displaySums"
total-sums="totalSums"
group-sums="groupSums"
class="list issues"
update-results="updateResults()"
with-loading="withLoading"
update-back-url="updateBackUrl">
</work-packages-table>
<table-pagination total-entries="totalEntries"
update-results="updateResults()">
</table-pagination>
<modal-loading>
</modal-loading>
<work-package-context-menu></work-package-context-menu>
<column-context-menu></column-context-menu>
</div>
<% end -%>

@ -1,43 +0,0 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= form_tag({ controller: '/queries', action: 'new' }, id: 'query_form', name: 'queryForm', novalidate: true) do %>
<%= hidden_field_tag('project_id', project.to_param) if project %>
<work-packages-options></work-packages-options>
<p class="buttons hide-when-print">
<filter-clear query="query">
</filter-clear>
<% if query.new_record? && User.current.allowed_to?(:save_queries, project, :global => true) %>
<a href="" ng-click="submitQueryForm()" class="icon icon-save1">Save</a>
<% end %>
</p>
<% end %>

@ -47,63 +47,6 @@ end
<%= include_gon %>
<div id="work-packages-index" ng-controller="WorkPackagesController">
<div class="toolbar-container">
<toolbar>
<selectable-title selected-title="selectedTitle"
reload-method="reloadQuery"
data-groups="dataGroups"
groups="groups">
</selectable-title>
<ul>
<li><button class="button_highlight" with-dropdown dropdown-id="tasksDropdown"><i class="icon-add icon4"></i>New Task<i class="icon-pulldown-arrow1 icon-dropdown"></i></button></li>
<li>
<button class="button"
ng-click="showFiltersOptions = !showFiltersOptions"
ng-class="{active: showFiltersOptions}">
<i class="icon-pin icon-buttons"></i>Filter
</button>
</li>
<li><button class="button last" with-dropdown dropdown-id="settingsDropdown"><i class="icon-settings"></i><i class="icon-pulldown-arrow1 icon-dropdown"></i></button></li>
</ul>
</toolbar>
</div>
<div class="dropdown dropdown-relative dropdown-anchor-right" id="tasksDropdown">
<ul class="dropdown-menu">
<li><a ng-href="" ng-repeat="(id, obj) in projectTypes">{{obj[1]}}</a></li>
</ul>
</div>
<div options-dropdown class="dropdown dropdown-relative dropdown-anchor-right" id="settingsDropdown">
<ul class="dropdown-menu">
<li><a href ng-click="showColumnsModal()">Columns…</a></li>
<li><a href ng-click="showSortingModal()">Sorting…</a></li>
<li><a href ng-click="showGroupingModal()">Group by ...</a></li>
<li><a href ng-click="toggleDisplaySums()">Display sums</a></li>
<li class="dropdown-divider"></li>
<li><a href ng-click="showSaveModal()">Save</a></li>
<li><a href ng-click="showSaveModal(true)">Save as</a></li>
<li><a href ng-click="showExportModal()">Export</a></li>
<li ng-show="!query.isNew()"><a href ng-click="showShareModal()">Share</a></li>
<li><a href ng-click="showSettingsModal()">Page settings</a></li>
</ul>
</div>
<div class="title-bar" ng-show="showFiltersOptions">
<div class="title-bar-extras">
<%= error_messages_for 'query' %>
<%= render partial: 'query_form', locals: { project: project, query: query } %>
</div>
</div>
<% if query.valid? %>
<%= render :partial => 'work_packages/list', :locals => { :query => query } %>
<% end %>
</div>
<%= call_hook(:view_work_packages_index_bottom, { :project => project, :query => query }) %>
<% content_for :sidebar do %>

@ -24,7 +24,8 @@
"mocha": "~1.14.0",
"angular-mocks": "~1.2.14",
"angular-scenario": "~1.2.14",
"chai": "~1.9.0"
"chai": "~1.9.0",
"angular-ui-router": "~0.2.10"
},
"resolutions": {
"select2": "3.3.2"

@ -38,7 +38,7 @@ Feature: Work Package Query
| name | position |
| Bug | 1 |
@javascript
@javascript @wip
Scenario: Create a query and give it a name
When I am already admin
And I go to the work packages index page for the project "project"
@ -49,7 +49,7 @@ Feature: Work Package Query
Then I should see "Query" within "#content"
And I should see "Successful creation."
@javascript
@javascript @wip
Scenario: Group on empty Value (Assignee)
Given the project "project" has 1 issue with the following:
| subject | issue1 |
@ -65,6 +65,7 @@ Feature: Work Package Query
And I should see "Successful creation."
And I should see "None" within "#content"
@wip
Scenario: Save Button should be visible for users with the proper rights
Given there is 1 user with the following:
| login | bob |
@ -79,6 +80,7 @@ Feature: Work Package Query
And I go to the work packages index page for the project "project"
Then I should see "Save" within "#query_form"
@wip
Scenario: Save Button should be invisible for users without the proper rights
Given there is 1 user with the following:
| login | alice |

@ -90,6 +90,7 @@ module.exports = function(config) {
"app/assets/javascripts/angular/controllers/timelines-controller.js",
"app/assets/javascripts/angular/controllers/work-packages-controller.js",
"app/assets/javascripts/angular/controllers/work-package-details-controller.js",
'app/assets/javascripts/date-en-US.js',

@ -0,0 +1,56 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('WorkPackageDetailsController', function() {
var scope;
var buildController;
beforeEach(module('openproject.workPackages.controllers'));
beforeEach(inject(function($rootScope, $controller, $timeout) {
scope = $rootScope.$new();
buildController = function() {
ctrl = $controller("WorkPackageDetailsController", {
$scope: scope,
$stateParams: { workPackageId: 99 }
});
// $timeout.flush();
};
}));
describe('initialisation', function() {
it('should initialise', function() {
buildController();
});
});
});

@ -29,15 +29,14 @@
/*jshint expr: true*/
describe('WorkPackagesController', function() {
var scope, ctrl, win, testWorkPackageService, testQueryService, testPaginationService;
var scope, ctrl, win, testProjectService, testWorkPackageService, testQueryService, testPaginationService;
var buildController;
beforeEach(module('openproject.workPackages.controllers', 'openproject.workPackages.services', 'ng-context-menu', 'btford.modal'));
beforeEach(inject(function($rootScope, $controller, $timeout) {
scope = $rootScope.$new();
win = {
location: { pathname: "" },
gon: { project_types: [] }
location: { pathname: "" }
};
var workPackageData = {
@ -49,6 +48,22 @@ describe('WorkPackagesController', function() {
var availableQueryiesData = {
};
var projectData = { embedded: { types: [] } };
var projectsData = [ projectData ];
testProjectService = {
getProject: function(identifier) {
return $timeout(function() {
return projectData;
}, 10);
},
getProjects: function(identifier) {
return $timeout(function() {
return projectsData;
}, 10);
}
}
testWorkPackageService = {
getWorkPackages: function () {
},
@ -115,6 +130,7 @@ describe('WorkPackagesController', function() {
settingsModal: {},
shareModal: {},
sortingModal: {},
ProjectService: testProjectService,
QueryService: testQueryService,
PaginationService: testPaginationService,
WorkPackageService: testWorkPackageService

@ -0,0 +1,83 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('ProjectService', function() {
var $httpBackend, ProjectService;
beforeEach(module('openproject.services', 'openproject.models'));
beforeEach(inject(function(_$httpBackend_, _ProjectService_) {
$httpBackend = _$httpBackend_;
ProjectService = _ProjectService_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('getProject', function() {
beforeEach(function() {
$httpBackend.when('GET', '/api/v3/projects/superProject')
.respond({
"project": {
"id": 99,
"name": "Super-Duper Project",
"parent_id": null,
"leaf?": true
}
});
})
it('sends a successful get request', function() {
$httpBackend.expectGET('/api/v3/projects/superProject');
var callback = sinon.spy(),
project = ProjectService.getProject('superProject').then(callback);
$httpBackend.flush();
expect(callback).to.have.been.calledWith(sinon.match({
name: "Super-Duper Project"
}));
});
it('sends a unsuccessful get request', function() {
$httpBackend.expectGET('/api/v3/projects/superProject').respond(401);
var success = sinon.spy(),
error = sinon.spy(),
project = ProjectService.getProject('superProject').then(success, error);
$httpBackend.flush();
expect(success).not.to.have.been.called;
expect(error).to.have.been.called;
});
});
});

@ -0,0 +1,68 @@
<div id="work-packages-index">
<div class="toolbar-container">
<toolbar>
<selectable-title selected-title="selectedTitle"
reload-method="reloadQuery"
data-groups="dataGroups"
groups="groups">
</selectable-title>
<ul>
<li><button class="button_highlight" with-dropdown dropdown-id="tasksDropdown"><i class="icon-add icon4"></i>New Task<i class="icon-pulldown-arrow1 icon-dropdown"></i></button></li>
<li>
<button class="button"
ng-click="showFiltersOptions = !showFiltersOptions"
ng-class="{active: showFiltersOptions}">
<i class="icon-pin icon-buttons"></i>Filter
</button>
</li>
<li>
<button class="button" ui-sref="work-packages.list" ui-sref-active="active">List</button>
<button class="button" ui-sref="work-packages.list.details({})" ui-sref-active="active">Details</button>
</li>
<li><button class="button last" with-dropdown dropdown-id="settingsDropdown"><i class="icon-settings"></i><i class="icon-pulldown-arrow1 icon-dropdown"></i></button></li>
</ul>
</toolbar>
</div>
<div class="dropdown dropdown-relative dropdown-anchor-right" id="tasksDropdown">
<ul class="dropdown-menu">
<li><a ng-href="work_packages/new?work_package[type_id]={{type.id}}" ng-repeat="type in availableTypes">{{type.name}}</a></li>
</ul>
</div>
<div options-dropdown class="dropdown dropdown-relative dropdown-anchor-right" id="settingsDropdown">
<ul class="dropdown-menu">
<li><a href ng-click="showColumnsModal()">Columns…</a></li>
<li><a href ng-click="showSortingModal()">Sorting…</a></li>
<li><a href ng-click="toggleDisplaySums()">Display sums</a></li>
<li class="dropdown-divider"></li>
<li><a href ng-click="showSaveModal()">Save</a></li>
<li><a href ng-click="showSaveModal(true)">Save as</a></li>
<li><a href ng-click="showExportModal()">Export</a></li>
<li ng-show="!query.isNew()"><a href ng-click="showShareModal()">Share</a></li>
</ul>
</div>
<div class="title-bar" ng-show="showFiltersOptions">
<div class="title-bar-extras">
<work-packages-options></work-packages-options>
<p class="buttons hide-when-print">
<filter-clear query="query">
</filter-clear>
<!-- TODO: serialize permission checks -->
<!-- User.current.allowed_to?(:save_queries, project, :global => true) -->
<a href="" ng-click="submitQueryForm()" class="icon icon-save1">Save</a>
<!-- end -->
</p>
</div>
</div>
<back-url></back-url>
<div ui-view></div>
</div>

@ -0,0 +1,12 @@
<hr>
<h3>{{ workPackage.type.name }}</h3>
<h2>{{ workPackage.subject }}</h2>
<p>TODO: details panel goes here</p>
<h3>Debug</h3>
<pre>
{{ workPackage | json }}
</pre>
<button class="button" ui-sref="work-packages.list">Close details</button>

@ -0,0 +1,31 @@
<div class="autoscroll">
<work-packages-table ng-if="rows && columns"
project-identifier="projectIdentifier"
columns="columns"
rows="rows"
query="query"
group-by="query.groupBy"
group-by-column="groupByColumn"
count-by-group="workPackageCountByGroup"
display-sums="query.displaySums"
total-sums="totalSums"
group-sums="groupSums"
update-results="updateResults()"
with-loading="withLoading"
update-back-url="updateBackUrl">
</work-packages-table>
<table-pagination total-entries="totalEntries"
update-results="updateResults()">
</table-pagination>
<modal-loading>
</modal-loading>
<work-package-context-menu></work-package-context-menu>
<column-context-menu></column-context-menu>
</div>
<div ui-view></div>

@ -1,26 +1,33 @@
<div id="column-context-menu" class="action-menu" ng-show="opened">
<ul class="menu">
<li ng-click="groupBy(column.name)">
<a href="#"><span ng-bind="I18n.t('js.work_packages.query.group_by')"/> {{column.title}}</a>
<li ng-click="groupBy(column.name)" class="has-no-icon">
<a href="#"><span ng-bind="I18n.t('js.work_packages.query.group_by')"/> <span ng-bind="column.title"/></a>
</li>
<li ng-click="sortAscending(column.name)">
<a href="#"><span ng-bind="I18n.t('js.label_sort_by')"/> <span ng-bind="column.title"/> <span ng-bind="I18n.t('js.label_ascending')"/></a>
<a href="#">
<i class="icon-action-menu icon-pulldown-arrow3"></i>
<span ng-bind="I18n.t('js.label_sort_by')"/> <span ng-bind="column.title"/> <span ng-bind="I18n.t('js.label_ascending')"/>
</a>
</li>
<li ng-click="sortDescending(column.name)">
<i class="icon-action-menu icon-pulldown-arrow1"></i>
<a href="#"><span ng-bind="I18n.t('js.label_sort_by')"/> <span ng-bind="column.title"/> <span ng-bind="I18n.t('js.label_descending')"/></a>
</li>
<li ng-click="moveLeft(column.name)">
<i class="icon-action-menu icon-paragraph-left"></i>
<a href="#"><span ng-bind="I18n.t('js.label_move_column_left')"/></a>
</li>
<li ng-click="moveRight(column.name)">
<i class="icon-action-menu icon-paragraph-right"></i>
<a href="#"><span ng-bind="I18n.t('js.label_move_column_right')"/></a>
</li>
<li ng-click="hideColumn(column.name)">
<i class="icon-action-menu icon-delete2"></i>
<a href="#"><span ng-bind="I18n.t('js.label_hide_column')"/></a>
</li>

@ -1,8 +1,8 @@
<div class="ng-modal-window">
<div class="ng-modal-inner">
<a href class="ng-modal-close" ng-click="modal.closeMe()">&nbsp;</a>
<div class="modal-header"><i class="icon-close" ng-click="modal.closeMe()"></i></div>
<h1>Columns</h1>
<h3>Columns</h3>
<div>
<label for="selected_columns">Selected Columns</label>
@ -16,8 +16,8 @@
</div>
<div>
<button ng-click="updateSelectedColumns()">Apply</button>
<button ng-click="modal.closeMe()">Cancel</button>
<button class="button_highlight" ng-click="updateSelectedColumns()">Apply</button>
<button class="button" ng-click="modal.closeMe()">Cancel</button>
</div>
</div>

@ -1,8 +1,8 @@
<div class="ng-modal-window">
<div class="ng-modal-inner">
<a href class="ng-modal-close" ng-click="modal.closeMe()">&nbsp;</a>
<div class="modal-header"><i class="icon-close" ng-click="modal.closeMe()"></i></div>
<h1>{{modal.name}}</h1>
<h3>{{modal.name}}</h3>
<div class="grid-items-lines">
<a ng-repeat="(key, value) in modal.formats" ng-href="{{value}}" class="grid-item">
<img src="" alt="">

@ -1,16 +1,16 @@
<div class="ng-modal-window">
<div class="ng-modal-inner">
<a href class="ng-modal-close" ng-click="modal.closeMe()">&nbsp;</a>
<div class="modal-header"><i class="icon-close" ng-click="modal.closeMe()"></i></div>
<h1>Save</h1>
<h3>Save</h3>
<div>
<label for="name">Name</label>
<input type="text" name="query_name" ng-model="queryName"></input>
<input class="short" type="text" name="query_name" ng-model="queryName"></input>
</div>
<div>
<button ng-click="modal.closeMe()">Cancel</button>
<button ng-click="saveQueryAs(queryName)">Save</button>
<button class="button_highlight" ng-click="saveQueryAs(queryName)">Save</button>
<button class="button" ng-click="modal.closeMe()">Cancel</button>
</div>
</div>

@ -1,8 +1,8 @@
<div class="ng-modal-window">
<div class="ng-modal-inner">
<a href class="ng-modal-close" ng-click="modal.closeMe()">&nbsp;</a>
<div class="modal-header"><i class="icon-close" ng-click="modal.closeMe()"></i></div>
<h1>Settings</h1>
<h3>Settings</h3>
</div>
</div>

@ -1,16 +1,16 @@
<div class="ng-modal-window">
<div class="ng-modal-inner">
<a href class="ng-modal-close" ng-click="modal.closeMe()">&nbsp;</a>
<div class="modal-header"><i class="icon-close" ng-click="modal.closeMe()"></i></div>
<h1>Share</h1>
<h3>Share</h3>
<div>
<label for="name">Page visible for others</label>
<input type="checkbox" name="is_public" ng-model="query.isPublic"></input>
</div>
<div>
<button ng-click="modal.closeMe()">Cancel</button>
<button ng-click="saveQuery()">Save</button>
<button ng-click="modal.closeMe()">Cancel</button>
</div>
</div>

@ -1,8 +1,24 @@
<div class="ng-modal-window">
<div class="ng-modal-inner">
<a href class="ng-modal-close" ng-click="modal.closeMe()">&nbsp;</a>
<div class="ng-modal-inner modal-content">
<div class="modal-header"><i class="icon-close" ng-click="modal.closeMe()"></i></div>
<h1>Sorting</h1>
<h3>Sorting</h3>
<div ng-repeat="element in sortElements">
<input type="hidden"
ui-select2-sortable="sortByOptions"
simple-query="getAvailableColumnsData"
ng-model="element[0]"></input>
<input type="hidden"
ui-select2-sortable="sortDirectionOptions"
simple-query="getDirectionsData"
ng-model="element[1]"></input>
</div>
<div>
<button ng-click="updateSortation()">Apply</button>
<button ng-click="modal.closeMe()">Cancel</button>
</div>
</div>
</div>

@ -12,7 +12,8 @@
<li class="folder priority">
<a href="#" class="context_item">TODO Priority</a>
<ul>
<i class="icon-pulldown-arrow4 icon-submenu"></i>
<ul class="sub-menu">
<li><a href="#" class=" disabled">Immediate</a></li>
<li><a href="#" class=" disabled">Urgent</a></li>
<li><a href="#" class=" disabled">High</a></li>

@ -1,4 +1,4 @@
<table id="work-packages-table">
<table class="workpackages-table">
<thead>
<tr>
<th class="checkbox hide-when-print">
@ -11,7 +11,7 @@
icon-name="yes"></icon-wrapper>
</a>
</th>
<th class="icon-table"><i class="icon-info"></i></th>
<th sort-header
header-name="'id'"
header-title="'#'"
@ -50,7 +50,7 @@
keyboard_hover: true
}"
id="group-header-{{ row.groupName }}">
<td colspan="{{ columns.length + 2 }}">
<td colspan="{{ columns.length + 3 }}">
<span ng-class="[
'expander',
'icon-context',
@ -95,8 +95,6 @@
ng-class="[
'hascontextmenu',
row.checked && 'context-menu-selection',
'issue',
$even && 'even' || 'odd',
!row.object['leaf?'] && 'parent' || '',
row.level > 0 && 'child idnt' || '',
row.level > 0 && ('idnt-' + row.level) || ''
@ -111,6 +109,10 @@
model="row.checked"/>
</td>
<td class="icon-table info">
<p><a href ui-sref="work-packages.list.details({workPackageId: row.object.id})"><i class="icon-info"></i></a></p>
</td>
<td class="id">
<span ng-if="workPackage.parent_id" class="hidden-for-sighted" ng-bind="parentWorkPackageHiddenText">
</span>
@ -140,7 +142,7 @@
'issue',
'work_package'
]">
<td colspan="2">
<td colspan="3">
{{ I18n.t('js.label_sum_for') }}
<span work-package-column work-package="row.object" column="groupByColumn"/>
</td>
@ -154,7 +156,7 @@
<tr work-package-total-sums
ng-if="displaySums"
class="sum group all issue work_package">
<td colspan="2">{{ I18n.t('js.label_sum_for') }} {{ I18n.t('js.label_all_work_packages') }}</td>
<td colspan="3">{{ I18n.t('js.label_sum_for') }} {{ I18n.t('js.label_all_work_packages') }}</td>
<td ng-repeat="column in columns">
{{ column.total_sum }}
</td>

@ -212,7 +212,7 @@ describe 'Custom field accessibility' do
include_context "index page with query"
it_behaves_like "localized table header"
pending # it_behaves_like "localized table header"
end
context "de" do
@ -220,7 +220,7 @@ describe 'Custom field accessibility' do
include_context "index page with query"
it_behaves_like "localized table header"
pending # it_behaves_like "localized table header"
end
end

@ -44,7 +44,7 @@ describe 'Work package index accessibility' do
describe 'Select all link' do
def select_all_link
find('table.list.issues th.checkbox a')
find('table.workpackages-table th.checkbox a')
end
def description_for_blind
@ -142,7 +142,7 @@ describe 'Work package index accessibility' do
describe 'id column' do
let(:link_caption) { '#' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th' }
let(:sort_header_selector) { 'table.workpackages-table th.checkbox + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
@ -152,7 +152,7 @@ describe 'Work package index accessibility' do
describe 'type column' do
let(:link_caption) { 'Type' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th + th' }
let(:sort_header_selector) { 'table.workpackages-table th.checkbox + th + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
@ -162,7 +162,7 @@ describe 'Work package index accessibility' do
describe 'status column' do
let(:link_caption) { 'Status' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th + th + th' }
let(:sort_header_selector) { 'table.workpackages-table th.checkbox + th + th + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
@ -172,7 +172,7 @@ describe 'Work package index accessibility' do
describe 'priority column' do
let(:link_caption) { 'Priority' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th + th + th + th' }
let(:sort_header_selector) { 'table.workpackages-table th.checkbox + th + th + th + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
@ -182,7 +182,7 @@ describe 'Work package index accessibility' do
describe 'subject column' do
let(:link_caption) { 'Subject' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th + th + th + th + th' }
let(:sort_header_selector) { 'table.workpackages-table th.checkbox + th + th + th + th + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
@ -192,7 +192,7 @@ describe 'Work package index accessibility' do
describe 'assigned to column' do
let(:link_caption) { 'Assignee' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th + th + th + th + th + th' }
let(:sort_header_selector) { 'table.workpackages-table th.checkbox + th + th + th + th + th + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'

Loading…
Cancel
Save