diff --git a/app/assets/javascripts/angular/controllers/dialogs/sorting.js b/app/assets/javascripts/angular/controllers/dialogs/sorting.js index 99d6e2e8e0..4f6a805235 100644 --- a/app/assets/javascripts/angular/controllers/dialogs/sorting.js +++ b/app/assets/javascripts/angular/controllers/dialogs/sorting.js @@ -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'}]; }]); diff --git a/app/assets/javascripts/angular/controllers/work-package-details-controller.js b/app/assets/javascripts/angular/controllers/work-package-details-controller.js new file mode 100644 index 0000000000..b4b86c0597 --- /dev/null +++ b/app/assets/javascripts/angular/controllers/work-package-details-controller.js @@ -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 : {}; + } + }); + } +]); diff --git a/app/assets/javascripts/angular/controllers/work-packages-controller.js b/app/assets/javascripts/angular/controllers/work-packages-controller.js index 2687a7c158..00bd62f9ea 100644 --- a/app/assets/javascripts/angular/controllers/work-packages-controller.js +++ b/app/assets/javascripts/angular/controllers/work-packages-controller.js @@ -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); diff --git a/app/assets/javascripts/angular/directives/work_packages/options-dropdown-directive.js b/app/assets/javascripts/angular/directives/work_packages/options-dropdown-directive.js index 96b652bebd..610899ff4d 100644 --- a/app/assets/javascripts/angular/directives/work_packages/options-dropdown-directive.js +++ b/app/assets/javascripts/angular/directives/work_packages/options-dropdown-directive.js @@ -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; diff --git a/app/assets/javascripts/angular/models/query.js b/app/assets/javascripts/angular/models/query.js index cd9ae3b1f3..aa13cdac5d 100644 --- a/app/assets/javascripts/angular/models/query.js +++ b/app/assets/javascripts/angular/models/query.js @@ -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; }, diff --git a/app/assets/javascripts/angular/models/sortation.js b/app/assets/javascripts/angular/models/sortation.js index 93f7172ce3..ebe67541d0 100644 --- a/app/assets/javascripts/angular/models/sortation.js +++ b/app/assets/javascripts/angular/models/sortation.js @@ -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'; diff --git a/app/assets/javascripts/angular/openproject-app.js b/app/assets/javascripts/angular/openproject-app.js index bcd4952217..9b6d63c80a 100644 --- a/app/assets/javascripts/angular/openproject-app.js +++ b/app/assets/javascripts/angular/openproject-app.js @@ -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){ diff --git a/app/assets/javascripts/angular/routing.js b/app/assets/javascripts/angular/routing.js new file mode 100644 index 0000000000..309949eca6 --- /dev/null +++ b/app/assets/javascripts/angular/routing.js @@ -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' + }) +}]); diff --git a/app/assets/javascripts/angular/services/project-service.js b/app/assets/javascripts/angular/services/project-service.js index c3b6a637db..888436c4a1 100644 --- a/app/assets/javascripts/angular/services/project-service.js +++ b/app/assets/javascripts/angular/services/project-service.js @@ -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() { diff --git a/app/assets/javascripts/angular/services/query-service.js b/app/assets/javascripts/angular/services/query-service.js index 7a873f9194..0114523006 100644 --- a/app/assets/javascripts/angular/services/query-service.js +++ b/app/assets/javascripts/angular/services/query-service.js @@ -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'; diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index 01b09d0453..5e64ece7de 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -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 diff --git a/app/assets/stylesheets/content/_action_menu_main.md b/app/assets/stylesheets/content/_action_menu_main.md index 6e7e392a0a..5f577bf271 100644 --- a/app/assets/stylesheets/content/_action_menu_main.md +++ b/app/assets/stylesheets/content/_action_menu_main.md @@ -2,50 +2,42 @@ ```
- +
- ``` diff --git a/app/assets/stylesheets/content/_action_menu_main.sass b/app/assets/stylesheets/content/_action_menu_main.sass index efc7784c11..dfc8f2d9c3 100644 --- a/app/assets/stylesheets/content/_action_menu_main.sass +++ b/app/assets/stylesheets/content/_action_menu_main.sass @@ -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 diff --git a/app/assets/stylesheets/content/_headings.md b/app/assets/stylesheets/content/_headings.md index 7ab8b064b8..b37bc18c1d 100644 --- a/app/assets/stylesheets/content/_headings.md +++ b/app/assets/stylesheets/content/_headings.md @@ -1 +1,13 @@ # Headings + +``` +

Headline H1

+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.

+

Headline H2

+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.

+

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.

+

Headline H4

+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.

+ +``` diff --git a/app/assets/stylesheets/content/_headings.sass b/app/assets/stylesheets/content/_headings.sass index 935f07e7cc..57ac1501e7 100644 --- a/app/assets/stylesheets/content/_headings.sass +++ b/app/assets/stylesheets/content/_headings.sass @@ -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 + + \ No newline at end of file diff --git a/app/assets/stylesheets/content/_modal.md b/app/assets/stylesheets/content/_modal.md index d8b4b4cdf7..a1f6d21402 100644 --- a/app/assets/stylesheets/content/_modal.md +++ b/app/assets/stylesheets/content/_modal.md @@ -1 +1,17 @@ # 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
+ + + + +``` diff --git a/app/assets/stylesheets/content/_modal.sass b/app/assets/stylesheets/content/_modal.sass index d390ca643d..babef96d4e 100644 --- a/app/assets/stylesheets/content/_modal.sass +++ b/app/assets/stylesheets/content/_modal.sass @@ -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 + \ No newline at end of file diff --git a/app/assets/stylesheets/fonts/_openproject_icon_font.sass b/app/assets/stylesheets/fonts/_openproject_icon_font.sass index d5d500a0ea..3aadddec12 100644 --- a/app/assets/stylesheets/fonts/_openproject_icon_font.sass +++ b/app/assets/stylesheets/fonts/_openproject_icon_font.sass @@ -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 diff --git a/app/assets/stylesheets/global/_mixins.sass b/app/assets/stylesheets/global/_mixins.sass index 2991f196a0..a902c67c0f 100644 --- a/app/assets/stylesheets/global/_mixins.sass +++ b/app/assets/stylesheets/global/_mixins.sass @@ -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 diff --git a/app/assets/stylesheets/global/_variables.sass b/app/assets/stylesheets/global/_variables.sass index c84e911bd3..b6a1154bff 100644 --- a/app/assets/stylesheets/global/_variables.sass +++ b/app/assets/stylesheets/global/_variables.sass @@ -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 diff --git a/app/assets/stylesheets/layout/_drop_down.sass b/app/assets/stylesheets/layout/_drop_down.sass index 35595d4903..31f07da0d9 100644 --- a/app/assets/stylesheets/layout/_drop_down.sass +++ b/app/assets/stylesheets/layout/_drop_down.sass @@ -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 diff --git a/app/controllers/api/v3/projects_controller.rb b/app/controllers/api/v3/projects_controller.rb index 6f19caf2a6..3bb958701f 100644 --- a/app/controllers/api/v3/projects_controller.rb +++ b/app/controllers/api/v3/projects_controller.rb @@ -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 diff --git a/app/controllers/work_packages_controller.rb b/app/controllers/work_packages_controller.rb index f182e85fae..8cb6da7d2a 100644 --- a/app/controllers/work_packages_controller.rb +++ b/app/controllers/work_packages_controller.rb @@ -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) diff --git a/app/views/layouts/angular.html.erb b/app/views/layouts/angular.html.erb new file mode 100644 index 0000000000..a654252fc8 --- /dev/null +++ b/app/views/layouts/angular.html.erb @@ -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. + +++#%> +<% show_decoration = params["layout"].nil? %> + + + +<%= html_title %> + + + + + +<%= 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_javascript_includes %> + +<%= call_hook :view_layouts_base_html_head %> + +<%= content_for(:header_tags) if content_for?(:header_tags) %> + + + +<% main_menu = render_main_menu(@project) %> +<% side_displayed = content_for?(:sidebar) || content_for?(:main_menu) || !main_menu.blank? %> + +
<%= (show_decoration) ? '' : 'nomenus' %>"> + <% if show_decoration %> +
+ +
+ <% end %> + +
<%= (show_decoration) ? '' : 'nomenus' %>"> + <% if (side_displayed && show_decoration) %> + + <% end %> + + <% if show_decoration %> + + <% end %> + +
<%= (show_decoration) ? '' : 'nomenus' %>" id="content"> + +
+ + <%= call_hook :view_layouts_base_content %> +
 
+
+
+ + <% if (show_decoration) %> + + <% end %> + + + +
+<%= call_hook :view_layouts_base_body_bottom %> + + diff --git a/app/views/work_packages/_list.html.erb b/app/views/work_packages/_list.html.erb deleted file mode 100644 index 6d3d929bb6..0000000000 --- a/app/views/work_packages/_list.html.erb +++ /dev/null @@ -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 -%> - -
- - - - - - - - - - - - - -
-<% end -%> diff --git a/app/views/work_packages/_query_form.html.erb b/app/views/work_packages/_query_form.html.erb deleted file mode 100644 index be15a3bdbe..0000000000 --- a/app/views/work_packages/_query_form.html.erb +++ /dev/null @@ -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 %> - - - -

- - - - <% if query.new_record? && User.current.allowed_to?(:save_queries, project, :global => true) %> - Save - <% end %> -

-<% end %> diff --git a/app/views/work_packages/index.html.erb b/app/views/work_packages/index.html.erb index 781ef64e5a..cf2faa913d 100644 --- a/app/views/work_packages/index.html.erb +++ b/app/views/work_packages/index.html.erb @@ -47,63 +47,6 @@ end <%= include_gon %> -
- -
- - - - -
    -
  • -
  • - -
  • -
  • -
-
-
- - - - - -
-
- <%= error_messages_for 'query' %> - <%= render partial: 'query_form', locals: { project: project, query: query } %> -
-
- - <% if query.valid? %> - <%= render :partial => 'work_packages/list', :locals => { :query => query } %> - <% end %> -
- <%= call_hook(:view_work_packages_index_bottom, { :project => project, :query => query }) %> <% content_for :sidebar do %> diff --git a/bower.json b/bower.json index fd206f262a..d7cb08a6d7 100644 --- a/bower.json +++ b/bower.json @@ -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" diff --git a/features/issues/query.feature b/features/issues/query.feature index 4c2e925e9c..f13dee7d27 100644 --- a/features/issues/query.feature +++ b/features/issues/query.feature @@ -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 | diff --git a/karma.conf.js b/karma.conf.js index b46315d193..1b95a8f2d6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -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', diff --git a/karma/tests/controllers/work-package-details-controller-test.js b/karma/tests/controllers/work-package-details-controller-test.js new file mode 100644 index 0000000000..c1f365802b --- /dev/null +++ b/karma/tests/controllers/work-package-details-controller-test.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(); + }); + }); + +}); diff --git a/karma/tests/controllers/work-packages-controller-test.js b/karma/tests/controllers/work-packages-controller-test.js index 2d8df67e2f..15fb784a43 100644 --- a/karma/tests/controllers/work-packages-controller-test.js +++ b/karma/tests/controllers/work-packages-controller-test.js @@ -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 diff --git a/karma/tests/services/project-service-test.js b/karma/tests/services/project-service-test.js new file mode 100644 index 0000000000..dbd384ac36 --- /dev/null +++ b/karma/tests/services/project-service-test.js @@ -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; + }); + }); +}); diff --git a/public/templates/work_packages.html b/public/templates/work_packages.html new file mode 100644 index 0000000000..17850fa3e5 --- /dev/null +++ b/public/templates/work_packages.html @@ -0,0 +1,68 @@ +
+ +
+ + + + +
    +
  • +
  • + +
  • +
  • + + +
  • +
  • +
+
+
+ + + + + +
+
+ + +

+ + + + + + Save + +

+
+
+ + + +
+ +
diff --git a/public/templates/work_packages.list.details.html b/public/templates/work_packages.list.details.html new file mode 100644 index 0000000000..b8372836cd --- /dev/null +++ b/public/templates/work_packages.list.details.html @@ -0,0 +1,12 @@ +
+ +

{{ workPackage.type.name }}

+

{{ workPackage.subject }}

+

TODO: details panel goes here

+ +

Debug

+
+{{ workPackage | json }}
+
+ + diff --git a/public/templates/work_packages.list.html b/public/templates/work_packages.list.html new file mode 100644 index 0000000000..04494830c5 --- /dev/null +++ b/public/templates/work_packages.list.html @@ -0,0 +1,31 @@ +
+ + + + + + + + + + + + + +
+ +
diff --git a/public/templates/work_packages/column_context_menu.html b/public/templates/work_packages/column_context_menu.html index 5de82594cd..f75f1f33b1 100644 --- a/public/templates/work_packages/column_context_menu.html +++ b/public/templates/work_packages/column_context_menu.html @@ -1,26 +1,33 @@