From 1e47c138cfc417a63b22ae4afba043846477d0ec Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Sat, 17 Oct 2015 18:17:59 +0200 Subject: [PATCH 01/66] Simplify component handling [ci skip] --- frontend/app/openproject-app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/app/openproject-app.js b/frontend/app/openproject-app.js index 01f2c50359..031508369c 100644 --- a/frontend/app/openproject-app.js +++ b/frontend/app/openproject-app.js @@ -296,3 +296,6 @@ var requireTemplate = require.context('./templates', true, /\.html$/); requireTemplate.keys().forEach(requireTemplate); require('!ngtemplate?module=openproject.templates!html!angular-busy/angular-busy.html'); + +var requireComponent = require.context('./components/', true, /\.(js|html)$/); +requireComponent.keys().forEach(requireComponent); From c144d5b3a7145ac8eebe4b8aea822c0f7d4504db Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Mon, 19 Oct 2015 14:48:25 +0200 Subject: [PATCH 02/66] Refactor HALAPIResource service --- frontend/app/api/hal-api-resource.js | 75 ------------------- .../services/hal-api-resource.service.js} | 44 +++++++++-- frontend/app/openproject-app.js | 2 - .../common/services}/hal-api-resource-test.js | 19 +---- 4 files changed, 42 insertions(+), 98 deletions(-) delete mode 100644 frontend/app/api/hal-api-resource.js rename frontend/app/{api/index.js => components/common/services/hal-api-resource.service.js} (59%) rename frontend/tests/unit/tests/{api => components/common/services}/hal-api-resource-test.js (85%) diff --git a/frontend/app/api/hal-api-resource.js b/frontend/app/api/hal-api-resource.js deleted file mode 100644 index b6a1e844d5..0000000000 --- a/frontend/app/api/hal-api-resource.js +++ /dev/null @@ -1,75 +0,0 @@ -//-- copyright -// OpenProject is a project management system. -// Copyright (C) 2012-2015 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. -//++ - -/* globals Hyperagent */ -require('hyperagent'); - -module.exports = function HALAPIResource($timeout, $q) { - 'use strict'; - var configure = function() { - Hyperagent.configure('ajax', function(settings) { - var deferred = $q.defer(), - resolve = settings.success, - reject = settings.error; - - settings.success = deferred.resolve; - settings.reject = deferred.reject; - - deferred.promise.then(function(response) { - $timeout(function() { resolve(response); }); - }, function(reason) { - $timeout(function() { reject(reason); }); - }); - - return jQuery.ajax(settings); - }); - Hyperagent.configure('defer', $q.defer); - // keep this if you want null values to not be overwritten by - // Hyperagent.js miniscore - // this weird line replaces HA miniscore with normal underscore - // Freud would be happy with what ('_', _) reminds me of - Hyperagent.configure('_', _); - }; - return { - setup: function(uri, params) { - if (!params) { - params = {}; - } - configure(); - - var link = new Hyperagent.Resource(_.extend({ - url: uri - }, params)); - if (params.method) { - link.props.href = uri; - link.props.method = params.method; - } - return link; - } - }; -}; diff --git a/frontend/app/api/index.js b/frontend/app/components/common/services/hal-api-resource.service.js similarity index 59% rename from frontend/app/api/index.js rename to frontend/app/components/common/services/hal-api-resource.service.js index bc505aeebe..22eced6d42 100644 --- a/frontend/app/api/index.js +++ b/frontend/app/components/common/services/hal-api-resource.service.js @@ -1,4 +1,4 @@ -//-- copyright +// -- copyright // OpenProject is a project management system. // Copyright (C) 2012-2015 the OpenProject Foundation (OPF) // @@ -24,10 +24,42 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // See doc/COPYRIGHT.rdoc for more details. -//++ +// ++ + +/* globals Hyperagent */ +require('hyperagent'); angular.module('openproject.api') - .factory('HALAPIResource', ['$timeout', - '$q', - require('./hal-api-resource') - ]); + .run(run) + .factory('HALAPIResource', HALAPIResource); + +run.$inject = ['$http', '$q']; + +function run($http, $q) { + Hyperagent.configure('ajax', function(settings) { + settings.transformResponse = function (data) { return data; }; + + return $http(settings).then( + function (response) { settings.success(response.data); }, + settings.error + ); + }); + Hyperagent.configure('defer', $q.defer); + Hyperagent.configure('_', _); +} + +function HALAPIResource () { + return { + setup: function(uri, params) { + params = params || {}; + var link = new Hyperagent.Resource(_.extend({ url: uri }, params)); + + if (params.method) { + link.props.href = uri; + link.props.method = params.method; + } + + return link; + } + }; +} diff --git a/frontend/app/openproject-app.js b/frontend/app/openproject-app.js index 031508369c..d76ecf4e45 100644 --- a/frontend/app/openproject-app.js +++ b/frontend/app/openproject-app.js @@ -271,8 +271,6 @@ openprojectApp } ]); -require('./api'); - angular.module('openproject.config') .service('ConfigurationService', [ 'PathHelper', diff --git a/frontend/tests/unit/tests/api/hal-api-resource-test.js b/frontend/tests/unit/tests/components/common/services/hal-api-resource-test.js similarity index 85% rename from frontend/tests/unit/tests/api/hal-api-resource-test.js rename to frontend/tests/unit/tests/components/common/services/hal-api-resource-test.js index c98741491a..73e1b49733 100644 --- a/frontend/tests/unit/tests/api/hal-api-resource-test.js +++ b/frontend/tests/unit/tests/components/common/services/hal-api-resource-test.js @@ -30,23 +30,12 @@ describe('HALAPIResource', function() { - var HALAPIResource, testPathHelper; + var HALAPIResource; beforeEach(module('openproject.api')); - beforeEach(function() { - testPathHelper = { - apiV3: '/api/v3', - appBasePath: '' - }; - - module(function($provide) { - $provide.value('PathHelper', testPathHelper); - }); - - inject(function(_HALAPIResource_) { - HALAPIResource = _HALAPIResource_; - }); - }); + beforeEach(inject(function(_HALAPIResource_) { + HALAPIResource = _HALAPIResource_; + })); describe('setup', function() { var apiResource, resourceFunction; From f57dc8e7342c5fc569ea497988cd9bf7ff4753c9 Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Thu, 22 Oct 2015 02:01:45 +0200 Subject: [PATCH 03/66] Allow tests/specs to be located under components/ This makes it possible to have tests in the same location as the components they belong to. --- .../common/services/hal-api-resource.service.test.js} | 8 +++----- frontend/app/openproject-app.js | 2 +- frontend/karma.conf.js | 4 +++- 3 files changed, 7 insertions(+), 7 deletions(-) rename frontend/{tests/unit/tests/components/common/services/hal-api-resource-test.js => app/components/common/services/hal-api-resource.service.test.js} (96%) diff --git a/frontend/tests/unit/tests/components/common/services/hal-api-resource-test.js b/frontend/app/components/common/services/hal-api-resource.service.test.js similarity index 96% rename from frontend/tests/unit/tests/components/common/services/hal-api-resource-test.js rename to frontend/app/components/common/services/hal-api-resource.service.test.js index 73e1b49733..bf6b24d2c9 100644 --- a/frontend/tests/unit/tests/components/common/services/hal-api-resource-test.js +++ b/frontend/app/components/common/services/hal-api-resource.service.test.js @@ -1,4 +1,4 @@ -//-- copyright +// -- copyright // OpenProject is a project management system. // Copyright (C) 2012-2015 the OpenProject Foundation (OPF) // @@ -24,14 +24,12 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // See doc/COPYRIGHT.rdoc for more details. -//++ - -/*jshint expr: true*/ +// ++ describe('HALAPIResource', function() { var HALAPIResource; - beforeEach(module('openproject.api')); + beforeEach(angular.mock.module('openproject.api')); beforeEach(inject(function(_HALAPIResource_) { HALAPIResource = _HALAPIResource_; diff --git a/frontend/app/openproject-app.js b/frontend/app/openproject-app.js index d76ecf4e45..95e1b5180e 100644 --- a/frontend/app/openproject-app.js +++ b/frontend/app/openproject-app.js @@ -295,5 +295,5 @@ requireTemplate.keys().forEach(requireTemplate); require('!ngtemplate?module=openproject.templates!html!angular-busy/angular-busy.html'); -var requireComponent = require.context('./components/', true, /\.(js|html)$/); +var requireComponent = require.context('./components/', true, /^((?!\.(test|spec)).)*\.(js|html)$/); requireComponent.keys().forEach(requireComponent); diff --git a/frontend/karma.conf.js b/frontend/karma.conf.js index 4c5c5bab61..8784e9d370 100644 --- a/frontend/karma.conf.js +++ b/frontend/karma.conf.js @@ -71,7 +71,9 @@ module.exports = function(config) { 'tests/unit/tests/asset_functions.js', 'tests/unit/tests/**/*test.js', - 'tests/unit/tests/legacy-tests.js' + 'tests/unit/tests/legacy-tests.js', + + 'app/components/**/*.test.js' ], From 5fbf27b7bcc9274dee999febe17114fbfa1d1622 Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Fri, 23 Oct 2015 14:54:59 +0200 Subject: [PATCH 04/66] Fix error that prevented protractor from starting [ci skip] --- frontend/tests/integration/protractor.conf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/tests/integration/protractor.conf.js b/frontend/tests/integration/protractor.conf.js index c024e906dd..84e11a01fd 100644 --- a/frontend/tests/integration/protractor.conf.js +++ b/frontend/tests/integration/protractor.conf.js @@ -35,7 +35,7 @@ exports.config = { 'browserName': 'firefox' }, - directConnect: false, + directConnect: true, specs: [ 'specs/*spec.js', From aa01eb1f79ae4ef7b7b2bd6b81f6be4aeeb32ca8 Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Sun, 4 Oct 2015 13:54:46 +0200 Subject: [PATCH 05/66] Implement work package editing in fullscreen view --- .../work-package-show-controller.js | 9 +-------- .../editable/inplace-editor-date-directive.js | 2 +- .../inplace-editor-date-range-directive.js | 2 +- .../inplace-editor-dropdown-directive.js | 2 +- .../inplace-editor-display-pane-directive.js | 4 +++- .../inplace-editor-edit-pane-directive.js | 19 ++++++++++++++++++- .../services/editable-fields-state.js | 15 ++++++++++++++- 7 files changed, 39 insertions(+), 14 deletions(-) diff --git a/frontend/app/work_packages/controllers/work-package-show-controller.js b/frontend/app/work_packages/controllers/work-package-show-controller.js index d73fed0e51..87debc10f8 100644 --- a/frontend/app/work_packages/controllers/work-package-show-controller.js +++ b/frontend/app/work_packages/controllers/work-package-show-controller.js @@ -390,14 +390,7 @@ module.exports = function($scope, ); $scope.editWorkPackage = function() { - // TODO: Copied from work-package-details-toolbar-directive - // since reusing the directive would break the existing toolbar - // markup. - var editWorkPackagePath = PathHelper.staticEditWorkPackagePath($scope.workPackage.props.id); - var backUrl = '?back_url=' + encodeURIComponent($location.url()); - - // TODO: Temporarily going to the old edit dialog until we get in-place editing done - window.location = editWorkPackagePath + backUrl; + EditableFieldsState.editAll.toggleState(); }; // Stuff copied from DetailsTabOverviewController diff --git a/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-date-directive.js b/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-date-directive.js index 74d4f9cc34..072bcf1c54 100644 --- a/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-date-directive.js +++ b/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-date-directive.js @@ -80,7 +80,7 @@ module.exports = function(WorkPackageFieldService, EditableFieldsState, }; $timeout(function() { - datepicker.focus(); + EditableFieldsState.editAll.state || datepicker.focus(); }); angular.element('.work-packages--details-content').on('click', function(e) { diff --git a/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-date-range-directive.js b/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-date-range-directive.js index d06a89fd28..019d0dc321 100644 --- a/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-date-range-directive.js +++ b/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-date-range-directive.js @@ -107,7 +107,7 @@ module.exports = function(TimezoneService, ConfigurationService, }; $timeout(function() { - startDatepicker.focus(); + EditableFieldsState.editAll.state || startDatepicker.focus(); }); startDatepicker.textbox.on('click focusin', function() { diff --git a/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-dropdown-directive.js b/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-dropdown-directive.js index ff5a963c4f..89d0ef196b 100644 --- a/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-dropdown-directive.js +++ b/frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-dropdown-directive.js @@ -86,7 +86,7 @@ module.exports = function( fieldController.state.isBusy = false; if (!EditableFieldsState.forcedEditState) { - FocusHelper.focusUiSelect(element); + EditableFieldsState.editAll.state || FocusHelper.focusUiSelect(element); } }); } diff --git a/frontend/app/work_packages/directives/inplace_editor/inplace-editor-display-pane-directive.js b/frontend/app/work_packages/directives/inplace_editor/inplace-editor-display-pane-directive.js index c3df069193..dcb3163910 100644 --- a/frontend/app/work_packages/directives/inplace_editor/inplace-editor-display-pane-directive.js +++ b/frontend/app/work_packages/directives/inplace_editor/inplace-editor-display-pane-directive.js @@ -107,7 +107,7 @@ module.exports = function( }, true); scope.$watch('fieldController.isEditing', function(isEditing, oldIsEditing) { - if (!isEditing) { + if (!isEditing && !fieldController.lockFocus) { $timeout(function() { if (oldIsEditing) { // check old value to not trigger focus on the first time @@ -118,6 +118,8 @@ module.exports = function( }); }); } + + fieldController.lockFocus = false; }); } }; diff --git a/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js b/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js index eaf02d91b7..5a3cbaf134 100644 --- a/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js +++ b/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js @@ -192,6 +192,16 @@ module.exports = function( }; showErrors(); } + + $scope.$watch('editableFieldsState.editAll.state', function(state) { + var field = $scope.fieldController.field; + $scope.fieldController.isEditing = state; + $scope.fieldController.lockFocus = true; + + if (EditableFieldsState.editAll.isFocusField(field)) { + vm.markActive(); + } + }); }, link: function(scope, element, attrs, fieldController) { scope.fieldController = fieldController; @@ -263,8 +273,15 @@ module.exports = function( }); scope.$watch('fieldController.isEditing', function(isEditing) { - if (isEditing && !EditableFieldsState.forcedEditState) { + var efs = EditableFieldsState, field = fieldController.field; + + if (isEditing && !efs.editAll.state && !efs.forcedEditState) { scope.focusInput(); + + } else if (efs.editAll.state && efs.editAll.isFocusField(field)) { + $timeout(function () { + element.find('.focus-input').focus()[0].select(); + }); } }); } diff --git a/frontend/app/work_packages/services/editable-fields-state.js b/frontend/app/work_packages/services/editable-fields-state.js index 1a2544eb80..d31ef8832c 100644 --- a/frontend/app/work_packages/services/editable-fields-state.js +++ b/frontend/app/work_packages/services/editable-fields-state.js @@ -33,6 +33,19 @@ module.exports = function() { isBusy: false, currentField: null, submissionPromises: {}, - forcedEditState: false + forcedEditState: false, + + editAll: { + focusField: 'subject', + state: false, + + toggleState: function () { + return this.state = !this.state; + }, + + isFocusField: function (field) { + return this.focusField === field; + } + } }; }; From 487448fffef83ea8f44721e12364e48d6e01d86d Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Wed, 7 Oct 2015 11:30:21 +0200 Subject: [PATCH 06/66] Upgrade protractor to v2.4.0 --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 9625d5a593..3d7ab08357 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,7 +35,7 @@ "mocha": "~1.18.2", "mocha-jenkins-reporter": "^0.1.2", "phantomjs": "~1.9.2", - "protractor": "^2.1.0", + "protractor": "^2.4.0", "sinon": "~1.9.1", "sinon-chai": "~2.5.0", "sorted-object": "^1.0.0", From 6e0e818dcb7ef9c678633d30197bed4e79cf983c Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Wed, 7 Oct 2015 17:37:00 +0200 Subject: [PATCH 07/66] Implement tests for WP fullscreen editing --- .../pages/work-package-show-page.js | 40 +++++++++++++ .../work-packages/work-package-edit-spec.js | 59 +++++++++++++++++++ .../unit/tests/work_packages/routing-test.js | 28 +++++++++ .../services/editable-fields-state-test.js | 48 +++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 frontend/tests/integration/pages/work-package-show-page.js create mode 100644 frontend/tests/integration/specs/work-packages/work-package-edit-spec.js create mode 100644 frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js diff --git a/frontend/tests/integration/pages/work-package-show-page.js b/frontend/tests/integration/pages/work-package-show-page.js new file mode 100644 index 0000000000..df37a35483 --- /dev/null +++ b/frontend/tests/integration/pages/work-package-show-page.js @@ -0,0 +1,40 @@ +//-- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2015 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. +//++ + +module.exports = function WorkPackageShowPage() { + + var wpId = 819; + + this.editButton = $('.button[title="Edit"]'); + this.focusElement = $('#work-package-subject .focus-input'); + + + this.get = function() { + browser.get('/work_packages/' + wpId + '/activity'); + }; +}; diff --git a/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js b/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js new file mode 100644 index 0000000000..e5545cf250 --- /dev/null +++ b/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js @@ -0,0 +1,59 @@ +// -- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2015 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. +// ++ + +var expect = require('../../spec_helper.js').expect, + WorkPackageShowPage = require('../../pages/work-package-show-page.js'); + +describe('Work package edit', function() { + var page = new WorkPackageShowPage(), + expectFocusEquals = function (id) { + var activeId = browser.driver.switchTo().activeElement().getId(); + expect(activeId).to.eventually.deep.equal(id); + + }; + + beforeEach(function () { + page.get(); + page.editButton.isPresent().then(function () { + page.editButton.click(); + }) + }); + + it('should focus the subject field when used', function() { + page.focusElement.getId().then(expectFocusEquals); + }); + + it('should show multiple editable input fields', function() { + expect($$('.focus-input').count()).to.eventually.be.above(1); + }); + + it('should reset previously edited fields without focusing one', function() { + page.editButton.click(); + page.editButton.getId().then(expectFocusEquals); + }); +}); diff --git a/frontend/tests/unit/tests/work_packages/routing-test.js b/frontend/tests/unit/tests/work_packages/routing-test.js index 945f03216f..bf33e6847b 100644 --- a/frontend/tests/unit/tests/work_packages/routing-test.js +++ b/frontend/tests/unit/tests/work_packages/routing-test.js @@ -1,3 +1,31 @@ +// -- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2015 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. +// ++ + describe('Routing', function () { var $rootScope, $state, mockState = { go: function () {} }; diff --git a/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js b/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js new file mode 100644 index 0000000000..c46a31bab9 --- /dev/null +++ b/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js @@ -0,0 +1,48 @@ +// -- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2015 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. +// ++ + +describe('EditableFieldsState service', function () { + var EditableFieldsState, eAll; + + beforeEach(module('openproject')); + + beforeEach(inject(function (_EditableFieldsState_) { + EditableFieldsState = _EditableFieldsState_; + eAll = EditableFieldsState.editAll; + })); + + describe('edit all', function () { + it('toggle state switches its current state', function () { + expect(EditableFieldsState.state === eAll.toggleState()).to.be.false; + }); + + it('matches its focused field', function () { + expect(eAll.isFocusField(eAll.focusField)).to.be.true; + }); + }); +}); From 880cd8348523af6423dc1575607ca945a7d3ddf0 Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Wed, 7 Oct 2015 18:59:27 +0200 Subject: [PATCH 08/66] Implement WP editing in list overview --- frontend/app/work_packages/directives/index.js | 2 +- .../directives/work-package-details-toolbar-directive.js | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/frontend/app/work_packages/directives/index.js b/frontend/app/work_packages/directives/index.js index dc5e90e766..d25b1882fe 100644 --- a/frontend/app/work_packages/directives/index.js +++ b/frontend/app/work_packages/directives/index.js @@ -85,7 +85,7 @@ angular.module('openproject.workPackages.directives') 'HookService', 'WorkPackageService', 'WorkPackageAuthorization', - 'PathHelper', + 'EditableFieldsState', require('./work-package-details-toolbar-directive') ]) .directive('workPackageDynamicAttribute', ['$compile', require( diff --git a/frontend/app/work_packages/directives/work-package-details-toolbar-directive.js b/frontend/app/work_packages/directives/work-package-details-toolbar-directive.js index 7c48147f9e..ba9ba8a2ee 100644 --- a/frontend/app/work_packages/directives/work-package-details-toolbar-directive.js +++ b/frontend/app/work_packages/directives/work-package-details-toolbar-directive.js @@ -34,7 +34,7 @@ module.exports = function(PERMITTED_MORE_MENU_ACTIONS, HookService, WorkPackageService, WorkPackageAuthorization, - PathHelper) { + EditableFieldsState) { function getPermittedActions(authorization, permittedMoreMenuActions) { var permittedActions = authorization.permittedActions(permittedMoreMenuActions); @@ -85,11 +85,7 @@ module.exports = function(PERMITTED_MORE_MENU_ACTIONS, scope.actionsAvailable = Object.keys(scope.permittedActions).length > 0; scope.editWorkPackage = function() { - var editWorkPackagePath = PathHelper.staticEditWorkPackagePath(scope.workPackage.props.id); - var backUrl = '?back_url=' + encodeURIComponent($location.url()); - - // TODO: Temporarily going to the old edit dialog until we get in-place editing done - window.location = editWorkPackagePath + backUrl; + EditableFieldsState.editAll.toggleState(); }; scope.triggerMoreMenuAction = function(action, link) { From 40167485a913ec07c74e6eac52f3272c7887eb4b Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Fri, 9 Oct 2015 15:53:18 +0200 Subject: [PATCH 09/66] Add work package edit actions toolbar (save & cancel) Saving the work package does not work as of yet. --- .../stylesheets/layout/_work_package.sass | 6 +++ .../templates/work_packages.list.details.html | 4 +- .../app/templates/work_packages.show.html | 6 ++- .../work_package_details_toolbar.html | 6 +-- .../work_package_edit_actions.html | 40 +++++++++++++++++ .../work-package-show-controller.js | 6 +-- .../app/work_packages/directives/index.js | 4 +- .../inplace-editor-edit-pane-directive.js | 5 --- .../work-package-details-toolbar-directive.js | 29 ++++++------ .../work-package-edit-actions-directive.js | 43 ++++++++++++++++++ .../services/editable-fields-state.js | 8 ++++ .../pages/work-package-show-page.js | 6 ++- .../work-packages/work-package-edit-spec.js | 44 ++++++++++++------- .../services/editable-fields-state-test.js | 8 ++++ 14 files changed, 168 insertions(+), 47 deletions(-) create mode 100644 frontend/app/templates/work_packages/work_package_edit_actions.html create mode 100644 frontend/app/work_packages/directives/work-package-edit-actions-directive.js diff --git a/app/assets/stylesheets/layout/_work_package.sass b/app/assets/stylesheets/layout/_work_package.sass index 88ef3f8a66..5c937b2883 100644 --- a/app/assets/stylesheets/layout/_work_package.sass +++ b/app/assets/stylesheets/layout/_work_package.sass @@ -178,6 +178,12 @@ .work-packages--attachments margin-bottom: 25px +.work-packages--edit-actions + @extend .work-packages--details-toolbar + + .work-packages--left-panel & + position: static + .work-package--attachments--files margin-bottom: 1rem diff --git a/frontend/app/templates/work_packages.list.details.html b/frontend/app/templates/work_packages.list.details.html index a4547cc2e3..22296695d2 100644 --- a/frontend/app/templates/work_packages.list.details.html +++ b/frontend/app/templates/work_packages.list.details.html @@ -54,6 +54,6 @@
- - + +
diff --git a/frontend/app/templates/work_packages.show.html b/frontend/app/templates/work_packages.show.html index f5773d2e3a..4ad0adc178 100644 --- a/frontend/app/templates/work_packages.show.html +++ b/frontend/app/templates/work_packages.show.html @@ -15,7 +15,8 @@
  • @@ -168,6 +169,9 @@ + + +
    diff --git a/frontend/app/templates/work_packages/work_package_details_toolbar.html b/frontend/app/templates/work_packages/work_package_details_toolbar.html index 52a4ea6652..368013a3c0 100644 --- a/frontend/app/templates/work_packages/work_package_details_toolbar.html +++ b/frontend/app/templates/work_packages/work_package_details_toolbar.html @@ -1,7 +1,5 @@ -
    - diff --git a/frontend/app/templates/work_packages/work_package_edit_actions.html b/frontend/app/templates/work_packages/work_package_edit_actions.html new file mode 100644 index 0000000000..c5028ef465 --- /dev/null +++ b/frontend/app/templates/work_packages/work_package_edit_actions.html @@ -0,0 +1,40 @@ + + +
    + + +
    diff --git a/frontend/app/work_packages/controllers/work-package-show-controller.js b/frontend/app/work_packages/controllers/work-package-show-controller.js index 87debc10f8..3a578736fd 100644 --- a/frontend/app/work_packages/controllers/work-package-show-controller.js +++ b/frontend/app/work_packages/controllers/work-package-show-controller.js @@ -64,6 +64,8 @@ module.exports = function($scope, $scope.can = AuthorisationService.can; $scope.cannot = AuthorisationService.cannot; + $scope.editAll = EditableFieldsState.editAll; + $scope.$on('$stateChangeSuccess', function(event, toState){ latestTab.registerState(toState.name); }); @@ -389,10 +391,6 @@ module.exports = function($scope, $scope.workPackage ); - $scope.editWorkPackage = function() { - EditableFieldsState.editAll.toggleState(); - }; - // Stuff copied from DetailsTabOverviewController var vm = this; diff --git a/frontend/app/work_packages/directives/index.js b/frontend/app/work_packages/directives/index.js index d25b1882fe..af52ba8e48 100644 --- a/frontend/app/work_packages/directives/index.js +++ b/frontend/app/work_packages/directives/index.js @@ -85,9 +85,11 @@ angular.module('openproject.workPackages.directives') 'HookService', 'WorkPackageService', 'WorkPackageAuthorization', - 'EditableFieldsState', require('./work-package-details-toolbar-directive') ]) + .directive('workPackageEditActions', [ + require('./work-package-edit-actions-directive') + ]) .directive('workPackageDynamicAttribute', ['$compile', require( './work-package-dynamic-attribute-directive')]) .directive('workPackageGroupHeader', require( diff --git a/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js b/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js index 5a3cbaf134..1f5b2fd83c 100644 --- a/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js +++ b/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js @@ -194,13 +194,8 @@ module.exports = function( } $scope.$watch('editableFieldsState.editAll.state', function(state) { - var field = $scope.fieldController.field; $scope.fieldController.isEditing = state; $scope.fieldController.lockFocus = true; - - if (EditableFieldsState.editAll.isFocusField(field)) { - vm.markActive(); - } }); }, link: function(scope, element, attrs, fieldController) { diff --git a/frontend/app/work_packages/directives/work-package-details-toolbar-directive.js b/frontend/app/work_packages/directives/work-package-details-toolbar-directive.js index ba9ba8a2ee..09c3099a0a 100644 --- a/frontend/app/work_packages/directives/work-package-details-toolbar-directive.js +++ b/frontend/app/work_packages/directives/work-package-details-toolbar-directive.js @@ -26,15 +26,15 @@ // See doc/COPYRIGHT.rdoc for more details. //++ -module.exports = function(PERMITTED_MORE_MENU_ACTIONS, - $state, - $window, - $location, - I18n, - HookService, - WorkPackageService, - WorkPackageAuthorization, - EditableFieldsState) { +module.exports = function( + PERMITTED_MORE_MENU_ACTIONS, + $state, + $window, + $location, + I18n, + HookService, + WorkPackageService, + WorkPackageAuthorization) { function getPermittedActions(authorization, permittedMoreMenuActions) { var permittedActions = authorization.permittedActions(permittedMoreMenuActions); @@ -76,7 +76,12 @@ module.exports = function(PERMITTED_MORE_MENU_ACTIONS, scope: { workPackage: '=' }, - link: function(scope, element, attributes) { + + controller: ['$scope', 'EditableFieldsState', function ($scope, EditableFieldsState) { + $scope.editAll = EditableFieldsState.editAll; + }], + + link: function(scope) { var authorization = new WorkPackageAuthorization(scope.workPackage); scope.I18n = I18n; @@ -84,10 +89,6 @@ module.exports = function(PERMITTED_MORE_MENU_ACTIONS, getPermittedPluginActions(authorization)); scope.actionsAvailable = Object.keys(scope.permittedActions).length > 0; - scope.editWorkPackage = function() { - EditableFieldsState.editAll.toggleState(); - }; - scope.triggerMoreMenuAction = function(action, link) { switch (action) { case 'delete': diff --git a/frontend/app/work_packages/directives/work-package-edit-actions-directive.js b/frontend/app/work_packages/directives/work-package-edit-actions-directive.js new file mode 100644 index 0000000000..2b27bf077e --- /dev/null +++ b/frontend/app/work_packages/directives/work-package-edit-actions-directive.js @@ -0,0 +1,43 @@ +// -- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2015 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. +// ++ + +module.exports = function () { + return { + restrict: 'E', + templateUrl: '/templates/work_packages/work_package_edit_actions.html', + scope: { + workPackage: '=' + }, + + controller: ['$scope', 'I18n', 'EditableFieldsState', + function ($scope, I18n, EditableFieldsState) { + $scope.I18n = I18n; + $scope.efs = EditableFieldsState; + }] + }; +}; diff --git a/frontend/app/work_packages/services/editable-fields-state.js b/frontend/app/work_packages/services/editable-fields-state.js index d31ef8832c..f17179bfad 100644 --- a/frontend/app/work_packages/services/editable-fields-state.js +++ b/frontend/app/work_packages/services/editable-fields-state.js @@ -39,6 +39,14 @@ module.exports = function() { focusField: 'subject', state: false, + start: function () { + return this.state = true; + }, + + stop: function () { + return this.state = false; + }, + toggleState: function () { return this.state = !this.state; }, diff --git a/frontend/tests/integration/pages/work-package-show-page.js b/frontend/tests/integration/pages/work-package-show-page.js index df37a35483..cc45a1cf2a 100644 --- a/frontend/tests/integration/pages/work-package-show-page.js +++ b/frontend/tests/integration/pages/work-package-show-page.js @@ -32,7 +32,11 @@ module.exports = function WorkPackageShowPage() { this.editButton = $('.button[title="Edit"]'); this.focusElement = $('#work-package-subject .focus-input'); - + this.editableFields = $$('.focus-input'); + this.editActions = { + container: $('.work-packages--edit-actions'), + cancel: $('.work-packages--edit-actions .button:last-child') + }; this.get = function() { browser.get('/work_packages/' + wpId + '/activity'); diff --git a/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js b/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js index e5545cf250..5963b8dccb 100644 --- a/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js +++ b/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js @@ -37,23 +37,37 @@ describe('Work package edit', function() { }; - beforeEach(function () { - page.get(); - page.editButton.isPresent().then(function () { - page.editButton.click(); - }) - }); + describe('when clicking edit button on show page', function () { + beforeEach(function () { + page.get(); + page.editButton.isPresent().then(function () { + page.editButton.click(); + }) + }); - it('should focus the subject field when used', function() { - page.focusElement.getId().then(expectFocusEquals); - }); + it('should focus the subject field when used', function() { + page.focusElement.getId().then(expectFocusEquals); + }); - it('should show multiple editable input fields', function() { - expect($$('.focus-input').count()).to.eventually.be.above(1); - }); + it('should show multiple editable input fields', function() { + expect(page.editableFields.count()).to.eventually.be.above(1); + }); + + it('should reset previously edited fields without focusing one', function() { + page.editButton.click(); + page.editButton.getId().then(expectFocusEquals); + expect(page.editableFields.count()).to.eventually.equal(0); + }); + + it('should show the edit actions', function () { + expect(page.editActions.container.isDisplayed()).to.eventually.be.true; + }); - it('should reset previously edited fields without focusing one', function() { - page.editButton.click(); - page.editButton.getId().then(expectFocusEquals); + describe('when triggering the edit actions', function () { + it('should cancel editing when the cancel button is clicked', function () { + page.editActions.cancel.click(); + expect(page.editableFields.count()).to.eventually.equal(0); + }); + }); }); }); diff --git a/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js b/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js index c46a31bab9..a7202019c0 100644 --- a/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js +++ b/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js @@ -41,6 +41,14 @@ describe('EditableFieldsState service', function () { expect(EditableFieldsState.state === eAll.toggleState()).to.be.false; }); + it('turns on editing on start', function () { + expect(eAll.start()).to.be.true; + }); + + it('turns off editing on stop', function () { + expect(eAll.stop()).to.be.false; + }); + it('matches its focused field', function () { expect(eAll.isFocusField(eAll.focusField)).to.be.true; }); From c751e0c23c7d4defb2542a77b8ee6753ffd229b3 Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Mon, 12 Oct 2015 08:58:31 +0200 Subject: [PATCH 10/66] Disable WP edit buttons for users without update permissions --- .../app/templates/work_packages.list.details.html | 2 +- frontend/app/templates/work_packages.show.html | 3 ++- .../work_packages/work_package_details_toolbar.html | 4 ++-- .../work_packages/work_package_edit_actions.html | 2 +- .../work-package-edit-actions-directive.js | 13 ++++++------- .../work_packages/services/editable-fields-state.js | 8 +++++++- .../services/editable-fields-state-test.js | 10 ++++++++++ 7 files changed, 29 insertions(+), 13 deletions(-) diff --git a/frontend/app/templates/work_packages.list.details.html b/frontend/app/templates/work_packages.list.details.html index 22296695d2..2447b2bc80 100644 --- a/frontend/app/templates/work_packages.list.details.html +++ b/frontend/app/templates/work_packages.list.details.html @@ -55,5 +55,5 @@
    - +
    diff --git a/frontend/app/templates/work_packages.show.html b/frontend/app/templates/work_packages.show.html index 4ad0adc178..0820957aa9 100644 --- a/frontend/app/templates/work_packages.show.html +++ b/frontend/app/templates/work_packages.show.html @@ -17,6 +17,7 @@ @@ -171,7 +172,7 @@ - +
    diff --git a/frontend/app/templates/work_packages/work_package_details_toolbar.html b/frontend/app/templates/work_packages/work_package_details_toolbar.html index 368013a3c0..ae56774f88 100644 --- a/frontend/app/templates/work_packages/work_package_details_toolbar.html +++ b/frontend/app/templates/work_packages/work_package_details_toolbar.html @@ -1,5 +1,5 @@ -
    - diff --git a/frontend/app/templates/work_packages/work_package_edit_actions.html b/frontend/app/templates/work_packages/work_package_edit_actions.html index c5028ef465..73c0b56582 100644 --- a/frontend/app/templates/work_packages/work_package_edit_actions.html +++ b/frontend/app/templates/work_packages/work_package_edit_actions.html @@ -28,7 +28,7 @@ ~ ++ --> -
    +
    diff --git a/frontend/app/work_packages/controllers/work-package-new-controller.js b/frontend/app/work_packages/controllers/work-package-new-controller.js index 5781d4726f..93e55129fc 100644 --- a/frontend/app/work_packages/controllers/work-package-new-controller.js +++ b/frontend/app/work_packages/controllers/work-package-new-controller.js @@ -117,7 +117,7 @@ module.exports = function( prepend: true, }; - WorkPackageFieldService.submitWorkPackageChanges(notify, function() { + EditableFieldsState.save(notify, function() { $rootScope.$emit('workPackagesRefreshRequired'); }); } diff --git a/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js b/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js index a0ee6cb433..1ddbfe3a4e 100644 --- a/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js +++ b/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js @@ -66,17 +66,14 @@ module.exports = function( // Propagate submission to all active fields // not contained in the workPackage.form (e.g., comment) this.submit = function(notify) { - WorkPackageFieldService.submitWorkPackageChanges( - notify, - function() { - // Clears the location hash, as we're now - // scrolling to somewhere else - $location.hash(null); - $timeout(function() { - $element[0].scrollIntoView(false); - }); - } - ); + EditableFieldsState.save(notify, function() { + // Clears the location hash, as we're now + // scrolling to somewhere else + $location.hash(null); + $timeout(function() { + $element[0].scrollIntoView(false); + }); + }); }; this.submitField = function(notify) { @@ -262,7 +259,8 @@ module.exports = function( } else if (efs.editAll.state && efs.editAll.isFocusField(field)) { $timeout(function () { - element.find('.focus-input').focus()[0].select(); + var focusElement = element.find('.focus-input'); + focusElement.length && focusElement.focus()[0].select(); }); } }); diff --git a/frontend/app/work_packages/directives/work-package-comment-directive.js b/frontend/app/work_packages/directives/work-package-comment-directive.js index 05730914cf..fb34ac5a46 100644 --- a/frontend/app/work_packages/directives/work-package-comment-directive.js +++ b/frontend/app/work_packages/directives/work-package-comment-directive.js @@ -73,11 +73,9 @@ module.exports = function( } var nextActivity = ctrl.activities.length + 1; - WorkPackageFieldService.submitWorkPackageChanges( - notify, - function() { - $location.hash('activity-' + (nextActivity)); - } + EditableFieldsState.save(notify, function() { + $location.hash('activity-' + (nextActivity)); + } ); }; diff --git a/frontend/app/work_packages/services/editable-fields-state.js b/frontend/app/work_packages/services/editable-fields-state.js index c50535ab49..60acb37c58 100644 --- a/frontend/app/work_packages/services/editable-fields-state.js +++ b/frontend/app/work_packages/services/editable-fields-state.js @@ -26,7 +26,7 @@ // See doc/COPYRIGHT.rdoc for more details. //++ -module.exports = function() { +module.exports = function($q, $rootScope, NotificationsService) { var EditableFieldsState = { workPackage: null, errors: null, @@ -44,6 +44,26 @@ module.exports = function() { return form.pendingChanges = form.pendingChanges || angular.copy(form.embedded.payload.props); }, + save: function (notify, callback) { + // We have to ensure that some promises are executed earlier then others + var promises = []; + angular.forEach(this.submissionPromises, function(field) { + var p = field.thePromise.call(this, notify); + promises[field.prepend ? 'unshift' : 'push' ](p); + }); + + return $q.all(promises).then(angular.bind(this, function() { + // Update work package after this call + $rootScope.$broadcast('workPackageRefreshRequired', callback); + this.errors = null; + this.submissionPromises = {}; + this.currentField = null; + this.editAll.stop(); + }), function(){ + NotificationsService.addError(I18n.t('js.work_packages.error_update_failed')); + }); + }, + editAll: { focusField: 'subject', state: false, diff --git a/frontend/app/work_packages/services/index.js b/frontend/app/work_packages/services/index.js index c379f38f76..53df2a4bc7 100644 --- a/frontend/app/work_packages/services/index.js +++ b/frontend/app/work_packages/services/index.js @@ -66,17 +66,18 @@ angular.module('openproject.workPackages.services') 'WorkPackagesHelper', '$q', '$http', - '$rootScope', '$timeout', '$filter', 'HookService', - 'NotificationsService', 'EditableFieldsState', require('./work-package-field-service') ]) - .service('EditableFieldsState', + .service('EditableFieldsState',[ + '$q', + '$rootScope', + 'NotificationsService', require('./editable-fields-state') - ) + ]) .service('WorkPackageAttachmentsService', [ 'Upload', // 'Upload' is provided by ngFileUpload 'PathHelper', diff --git a/frontend/app/work_packages/services/work-package-field-service.js b/frontend/app/work_packages/services/work-package-field-service.js index c714ff7da5..11abaf5846 100644 --- a/frontend/app/work_packages/services/work-package-field-service.js +++ b/frontend/app/work_packages/services/work-package-field-service.js @@ -32,11 +32,9 @@ module.exports = function( WorkPackagesHelper, $q, $http, - $rootScope, $timeout, $filter, HookService, - NotificationsService, EditableFieldsState ) { @@ -397,29 +395,6 @@ module.exports = function( return WorkPackagesHelper.formatValue(value, mappings[field]); } - function submitWorkPackageChanges(notify, callback) { - // We have to ensure that some promises are executed earlier then others - var promises = []; - angular.forEach(EditableFieldsState.submissionPromises, function(field) { - var p = field.thePromise.call(this, notify); - if (field.prepend) { - promises.unshift(p); - } else { - promises.push(p); - } - }); - - $q.all(promises).then(function() { - // Update work package after this call - $rootScope.$emit('workPackageRefreshRequired', callback); - EditableFieldsState.errors = null; - EditableFieldsState.submissionPromises = {}; - EditableFieldsState.currentField = null; - }, function(){ - NotificationsService.addError(I18n.t('js.work_packages.error_update_failed')); - }); - } - var WorkPackageFieldService = { getSchema: getSchema, isEditable: isEditable, @@ -436,8 +411,7 @@ module.exports = function( format: format, getInplaceEditStrategy: getInplaceEditStrategy, getInplaceDisplayStrategy: getInplaceDisplayStrategy, - defaultPlaceholder: '-', - submitWorkPackageChanges: submitWorkPackageChanges + defaultPlaceholder: '-' }; return WorkPackageFieldService; diff --git a/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js b/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js index 5963b8dccb..e6d0f4b2ca 100644 --- a/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js +++ b/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js @@ -53,12 +53,6 @@ describe('Work package edit', function() { expect(page.editableFields.count()).to.eventually.be.above(1); }); - it('should reset previously edited fields without focusing one', function() { - page.editButton.click(); - page.editButton.getId().then(expectFocusEquals); - expect(page.editableFields.count()).to.eventually.equal(0); - }); - it('should show the edit actions', function () { expect(page.editActions.container.isDisplayed()).to.eventually.be.true; }); diff --git a/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js b/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js index 5bbcb86934..8b4e9b93c8 100644 --- a/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js +++ b/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js @@ -54,6 +54,22 @@ describe('EditableFieldsState service', function () { }); }); + describe('save action', function () { + var $rootScope; + + beforeEach(inject(function (_$rootScope_) { + $rootScope = _$rootScope_; + })); + + it('should call the "workPackageRefreshRequired" event', function () { + var spy = sinon.spy($rootScope.$broadcast); + + EditableFieldsState.save(false, false).then(function () { + expect(spy.withArgs('workPackageRefreshRequired', false).calledOnce).to.be.true + }); + }); + }); + describe('edit all', function () { beforeEach(function () { EditableFieldsState.workPackage = { links: {} }; From 42e27e41a3c7c765b8440f36a128f481c7477873 Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Tue, 13 Oct 2015 11:32:55 +0200 Subject: [PATCH 14/66] Make user input stay when switching between views --- .../work-package-field-directive.js | 9 ++++--- .../services/editable-fields-state.js | 21 +++++++++++++++- .../pages/work-package-show-page.js | 25 +++++++++++-------- .../work-packages/work-package-edit-spec.js | 12 ++++++++- .../services/editable-fields-state-test.js | 17 +++++++++++++ 5 files changed, 69 insertions(+), 15 deletions(-) diff --git a/frontend/app/work_packages/directives/work-package-field-directive.js b/frontend/app/work_packages/directives/work-package-field-directive.js index 7ad90950cd..9440084518 100644 --- a/frontend/app/work_packages/directives/work-package-field-directive.js +++ b/frontend/app/work_packages/directives/work-package-field-directive.js @@ -44,9 +44,8 @@ module.exports = function(WorkPackageFieldService, EditableFieldsState) { }; this.updateWriteValue = function() { - this.writeValue = _.cloneDeep(WorkPackageFieldService.getValue( - EditableFieldsState.workPackage, - this.field + this.writeValue = EditableFieldsState.editAll.getFieldValue(this.field) + || _.cloneDeep(WorkPackageFieldService.getValue(EditableFieldsState.workPackage, this.field )); }; @@ -56,6 +55,10 @@ module.exports = function(WorkPackageFieldService, EditableFieldsState) { this.updateWriteValue(); this.editTitle = I18n.t('js.inplace.button_edit', { attribute: this.getLabel() }); } + + $scope.$watch('fieldController.writeValue', angular.bind(this, function (newValue) { + EditableFieldsState.editAll.addFieldValue(this.field, newValue); + })) } return { diff --git a/frontend/app/work_packages/services/editable-fields-state.js b/frontend/app/work_packages/services/editable-fields-state.js index 60acb37c58..55d5ce697c 100644 --- a/frontend/app/work_packages/services/editable-fields-state.js +++ b/frontend/app/work_packages/services/editable-fields-state.js @@ -27,6 +27,8 @@ //++ module.exports = function($q, $rootScope, NotificationsService) { + var editAllState = false; + var EditableFieldsState = { workPackage: null, errors: null, @@ -66,7 +68,24 @@ module.exports = function($q, $rootScope, NotificationsService) { editAll: { focusField: 'subject', - state: false, + fieldValues: {}, + + get state() { + return editAllState; + }, + + set state(state) { + editAllState = state; + if (!state) this.fieldValues = {}; + }, + + addFieldValue: function (field, value) { + this.fieldValues[field] = value; + }, + + getFieldValue: function (field) { + return this.fieldValues[field]; + }, get allowed() { return EditableFieldsState.workPackage && !!EditableFieldsState.workPackage.links.update; diff --git a/frontend/tests/integration/pages/work-package-show-page.js b/frontend/tests/integration/pages/work-package-show-page.js index cc45a1cf2a..6c759f6ef4 100644 --- a/frontend/tests/integration/pages/work-package-show-page.js +++ b/frontend/tests/integration/pages/work-package-show-page.js @@ -26,19 +26,24 @@ // See doc/COPYRIGHT.rdoc for more details. //++ -module.exports = function WorkPackageShowPage() { +function WorkPackageShowPage() {} - var wpId = 819; +WorkPackageShowPage.prototype = { - this.editButton = $('.button[title="Edit"]'); - this.focusElement = $('#work-package-subject .focus-input'); - this.editableFields = $$('.focus-input'); - this.editActions = { + wpId: 819, + editButton: $('.button[title="Edit"]'), + focusElement: $('#work-package-subject .focus-input'), + overviewButton: $('#work-packages-details-view-button'), + editableFields: $$('.focus-input'), + + editActions: { container: $('.work-packages--edit-actions'), cancel: $('.work-packages--edit-actions .button:last-child') - }; + }, - this.get = function() { - browser.get('/work_packages/' + wpId + '/activity'); - }; + get: function() { + browser.get('/work_packages/' + this.wpId + '/activity'); + } }; + +module.exports = WorkPackageShowPage; diff --git a/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js b/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js index e6d0f4b2ca..0bc7436cf7 100644 --- a/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js +++ b/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js @@ -34,7 +34,6 @@ describe('Work package edit', function() { expectFocusEquals = function (id) { var activeId = browser.driver.switchTo().activeElement().getId(); expect(activeId).to.eventually.deep.equal(id); - }; describe('when clicking edit button on show page', function () { @@ -57,6 +56,17 @@ describe('Work package edit', function() { expect(page.editActions.container.isDisplayed()).to.eventually.be.true; }); + it('should keep the user input when switching to overview mode', function () { + var val = 'my_value'; + page.focusElement.sendKeys(val); + + page.overviewButton.click().then(function () { + page.focusElement.isPresent().then(function () { + expect(page.focusElement.getAttribute('value')).to.eventually.equal(val); + }); + }); + }); + describe('when triggering the edit actions', function () { it('should cancel editing when the cancel button is clicked', function () { page.editActions.cancel.click(); diff --git a/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js b/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js index 8b4e9b93c8..9916e1fc0c 100644 --- a/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js +++ b/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js @@ -100,5 +100,22 @@ describe('EditableFieldsState service', function () { it('is not allowed if th WP update action does not exist', function () { expect(eAll.allowed).to.be.false; }); + + describe('field value storage', function () { + var field = 'my_field', value = 'my_val'; + + beforeEach(function () { + eAll.addFieldValue(field, value); + }); + + it('saves and retrieves the correct value', function () { + expect(eAll.getFieldValue(field)).to.equal(value); + }); + + it('clears stored values when edit all mode is turned off', function () { + eAll.stop(); + expect(eAll.getFieldValue(field)).to.be.falsy; + }); + }); }); }); From 1d8341fdbdc5c43206843510c5ea6e08dda97c41 Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Tue, 13 Oct 2015 12:26:46 +0200 Subject: [PATCH 15/66] Reset user input when cancel button is used in edit mode --- .../inplace-editor-edit-pane-directive.js | 2 ++ .../pages/work-package-show-page.js | 1 + .../work-packages/work-package-edit-spec.js | 19 +++++++++++++------ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js b/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js index 1ddbfe3a4e..1a9bf023aa 100644 --- a/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js +++ b/frontend/app/work_packages/directives/inplace_editor/inplace-editor-edit-pane-directive.js @@ -182,6 +182,8 @@ module.exports = function( $scope.$watch('editableFieldsState.editAll.state', function(state) { $scope.fieldController.isEditing = state; $scope.fieldController.lockFocus = true; + + !state && $scope.fieldController.updateWriteValue(); }); }, link: function(scope, element, attrs, fieldController) { diff --git a/frontend/tests/integration/pages/work-package-show-page.js b/frontend/tests/integration/pages/work-package-show-page.js index 6c759f6ef4..0db21cb135 100644 --- a/frontend/tests/integration/pages/work-package-show-page.js +++ b/frontend/tests/integration/pages/work-package-show-page.js @@ -33,6 +33,7 @@ WorkPackageShowPage.prototype = { wpId: 819, editButton: $('.button[title="Edit"]'), focusElement: $('#work-package-subject .focus-input'), + focusElementValue: $('#work-package-subject span.inplace-edit--read-value > span:first-child'), overviewButton: $('#work-packages-details-view-button'), editableFields: $$('.focus-input'), diff --git a/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js b/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js index 0bc7436cf7..0263733a53 100644 --- a/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js +++ b/frontend/tests/integration/specs/work-packages/work-package-edit-spec.js @@ -34,13 +34,16 @@ describe('Work package edit', function() { expectFocusEquals = function (id) { var activeId = browser.driver.switchTo().activeElement().getId(); expect(activeId).to.eventually.deep.equal(id); - }; + }, val; describe('when clicking edit button on show page', function () { beforeEach(function () { + val = 'my_value'; + page.get(); page.editButton.isPresent().then(function () { page.editButton.click(); + page.focusElement.sendKeys(val); }) }); @@ -57,9 +60,6 @@ describe('Work package edit', function() { }); it('should keep the user input when switching to overview mode', function () { - var val = 'my_value'; - page.focusElement.sendKeys(val); - page.overviewButton.click().then(function () { page.focusElement.isPresent().then(function () { expect(page.focusElement.getAttribute('value')).to.eventually.equal(val); @@ -67,11 +67,18 @@ describe('Work package edit', function() { }); }); - describe('when triggering the edit actions', function () { - it('should cancel editing when the cancel button is clicked', function () { + describe('when triggering the cancel action', function () { + beforeEach(function () { page.editActions.cancel.click(); + }); + + it('should cancel editing', function () { expect(page.editableFields.count()).to.eventually.equal(0); }); + + it('should reset user input', function () { + expect(page.focusElementValue.getText()).to.eventually.not.equal(val); + }); }); }); }); From 7cdb75c2145815d7f388750b5a64305aceb516df Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Tue, 13 Oct 2015 13:32:30 +0200 Subject: [PATCH 16/66] Change appearance of edit mode action buttons --- .../stylesheets/_work_packages_show_view_overwrite.scss | 5 +++++ frontend/app/templates/work_packages.show.html | 5 ++--- .../work_packages/work_package_edit_actions.html | 9 +++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/_work_packages_show_view_overwrite.scss b/app/assets/stylesheets/_work_packages_show_view_overwrite.scss index 8f525c2f95..15ac7e30fe 100644 --- a/app/assets/stylesheets/_work_packages_show_view_overwrite.scss +++ b/app/assets/stylesheets/_work_packages_show_view_overwrite.scss @@ -216,6 +216,11 @@ body.controller-work_packages.action-show { .activity-comment { margin-top: 15px; } + + .button.icon-edit.ng-hide { + display: block !important; + visibility: hidden; + } } .nosidebar { diff --git a/frontend/app/templates/work_packages.show.html b/frontend/app/templates/work_packages.show.html index 347f9d6d45..93be41e3a4 100644 --- a/frontend/app/templates/work_packages.show.html +++ b/frontend/app/templates/work_packages.show.html @@ -14,12 +14,11 @@
  • -
  • diff --git a/frontend/app/templates/work_packages/work_package_edit_actions.html b/frontend/app/templates/work_packages/work_package_edit_actions.html index dbfca34d9a..b56a2adc35 100644 --- a/frontend/app/templates/work_packages/work_package_edit_actions.html +++ b/frontend/app/templates/work_packages/work_package_edit_actions.html @@ -29,12 +29,13 @@ -->
    - - +
    From e92a154725c638dd0249b65737c3740bb3053e29 Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Wed, 14 Oct 2015 09:46:09 +0200 Subject: [PATCH 17/66] Implement cancel button for fullscreen edit Input values are set to their initial values when one restarts editing. --- .../work_package_edit_actions.html | 2 +- .../work-package-field-directive.js | 5 ++--- .../services/editable-fields-state.js | 20 +++++++------------ .../services/editable-fields-state-test.js | 4 ++-- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/frontend/app/templates/work_packages/work_package_edit_actions.html b/frontend/app/templates/work_packages/work_package_edit_actions.html index b56a2adc35..3518834b02 100644 --- a/frontend/app/templates/work_packages/work_package_edit_actions.html +++ b/frontend/app/templates/work_packages/work_package_edit_actions.html @@ -35,7 +35,7 @@ - diff --git a/frontend/app/work_packages/directives/work-package-field-directive.js b/frontend/app/work_packages/directives/work-package-field-directive.js index 9440084518..874f9cbdf5 100644 --- a/frontend/app/work_packages/directives/work-package-field-directive.js +++ b/frontend/app/work_packages/directives/work-package-field-directive.js @@ -44,9 +44,8 @@ module.exports = function(WorkPackageFieldService, EditableFieldsState) { }; this.updateWriteValue = function() { - this.writeValue = EditableFieldsState.editAll.getFieldValue(this.field) - || _.cloneDeep(WorkPackageFieldService.getValue(EditableFieldsState.workPackage, this.field - )); + this.writeValue = EditableFieldsState.editAll.getFieldValue(this.field) || _.cloneDeep( + WorkPackageFieldService.getValue(EditableFieldsState.workPackage, this.field)); }; if (this.isEditable()) { diff --git a/frontend/app/work_packages/services/editable-fields-state.js b/frontend/app/work_packages/services/editable-fields-state.js index 55d5ce697c..529ddd56fd 100644 --- a/frontend/app/work_packages/services/editable-fields-state.js +++ b/frontend/app/work_packages/services/editable-fields-state.js @@ -27,8 +27,6 @@ //++ module.exports = function($q, $rootScope, NotificationsService) { - var editAllState = false; - var EditableFieldsState = { workPackage: null, errors: null, @@ -36,11 +34,11 @@ module.exports = function($q, $rootScope, NotificationsService) { currentField: null, submissionPromises: {}, forcedEditState: false, - + isActiveField: function (field) { return !(this.forcedEditState || this.editAll.state) && this.currentField === field; }, - + getPendingFormChanges: function () { var form = this.workPackage.form; return form.pendingChanges = form.pendingChanges || angular.copy(form.embedded.payload.props); @@ -70,15 +68,6 @@ module.exports = function($q, $rootScope, NotificationsService) { focusField: 'subject', fieldValues: {}, - get state() { - return editAllState; - }, - - set state(state) { - editAllState = state; - if (!state) this.fieldValues = {}; - }, - addFieldValue: function (field, value) { this.fieldValues[field] = value; }, @@ -87,6 +76,11 @@ module.exports = function($q, $rootScope, NotificationsService) { return this.fieldValues[field]; }, + cancel: function () { + this.stop(); + this.fieldValues = {}; + }, + get allowed() { return EditableFieldsState.workPackage && !!EditableFieldsState.workPackage.links.update; }, diff --git a/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js b/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js index 9916e1fc0c..62e4738611 100644 --- a/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js +++ b/frontend/tests/unit/tests/work_packages/services/editable-fields-state-test.js @@ -112,8 +112,8 @@ describe('EditableFieldsState service', function () { expect(eAll.getFieldValue(field)).to.equal(value); }); - it('clears stored values when edit all mode is turned off', function () { - eAll.stop(); + it('clears stored values when cancel is called', function () { + eAll.cancel(); expect(eAll.getFieldValue(field)).to.be.falsy; }); }); From 05b13fa40b685238d391900e823ec11fc855dba0 Mon Sep 17 00:00:00 2001 From: Alex Dik Date: Wed, 14 Oct 2015 10:45:57 +0200 Subject: [PATCH 18/66] Change edit action toolbar appearance --- .../stylesheets/layout/_work_package.sass | 8 ++++- .../app/templates/work_packages.show.html | 2 +- .../work_package_edit_actions.html | 32 +------------------ 3 files changed, 9 insertions(+), 33 deletions(-) diff --git a/app/assets/stylesheets/layout/_work_package.sass b/app/assets/stylesheets/layout/_work_package.sass index 5c937b2883..c94357e973 100644 --- a/app/assets/stylesheets/layout/_work_package.sass +++ b/app/assets/stylesheets/layout/_work_package.sass @@ -182,7 +182,13 @@ @extend .work-packages--details-toolbar .work-packages--left-panel & - position: static + position: absolute + width: calc(60% + 7px) + left: -20px + padding: 0 20px .5rem + +.edit-all-mode .work-packages--left-panel + padding-bottom: 50px .work-package--attachments--files margin-bottom: 1rem diff --git a/frontend/app/templates/work_packages.show.html b/frontend/app/templates/work_packages.show.html index 93be41e3a4..c23d6d9ba4 100644 --- a/frontend/app/templates/work_packages.show.html +++ b/frontend/app/templates/work_packages.show.html @@ -1,4 +1,4 @@ -
    +