commit
6b39490e37
@ -1,85 +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. |
||||
#++ |
||||
|
||||
Feature: Issue edit |
||||
Background: |
||||
Given there is 1 project with the following: |
||||
| identifier | omicronpersei8 | |
||||
| name | omicronpersei8 | |
||||
And I am working in project "omicronpersei8" |
||||
And the project "omicronpersei8" has the following types: |
||||
| name | position | |
||||
| Bug | 1 | |
||||
And there is a default issuepriority with: |
||||
| name | Normal | |
||||
And there is a role "member" |
||||
And the role "member" may have the following rights: |
||||
| view_work_packages | |
||||
| edit_work_packages | |
||||
| add_work_package_notes | |
||||
And there is 1 user with the following: |
||||
| login | bob| |
||||
And the user "bob" is a "member" in the project "omicronpersei8" |
||||
And there are the following issue status: |
||||
| name | is_closed | is_default | |
||||
| New | false | true | |
||||
Given the user "bob" has 1 issue with the following: |
||||
| subject | issue1 | |
||||
| description | Aioli Sali Grande | |
||||
And I am already logged in as "bob" |
||||
|
||||
@javascript |
||||
Scenario: User updates an issue successfully |
||||
When I go to the page of the issue "issue1" |
||||
And I click on the edit button |
||||
Then I fill in "Notes" with "human Horn" |
||||
And I submit the form by the "Submit" button |
||||
And I should see "Successful update." within ".notice" |
||||
And I should see "human Horn" within ".work-package-details-activities-list" |
||||
|
||||
@javascript |
||||
Scenario: User updates an issue with previewing the stuff before |
||||
When I go to the page of the issue "issue1" |
||||
And I click on the edit button |
||||
Then I fill in "Notes" with "human Horn" |
||||
When I follow "Preview" |
||||
Then I should see "human Horn" within "#preview" |
||||
And I submit the form by the "Submit" button |
||||
And I should see "Successful update." within ".notice" |
||||
And I should see "human Horn" within ".work-package-details-activities-list" |
||||
|
||||
@javascript |
||||
Scenario: On an issue with children a user should not be able to change attributes which are overridden by children |
||||
Given the user "bob" has 1 issue with the following: |
||||
| subject | child1 | |
||||
When I go to the edit page of the work package "issue1" |
||||
Then there should not be a "Progress \(%\)" field |
||||
And there should be a disabled "Priority" field |
||||
And there should be a disabled "Start date" field |
||||
And there should be a disabled "Due date" field |
||||
And there should be a disabled "Estimated time" field |
@ -1,70 +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. |
||||
#++ |
||||
|
||||
Feature: Adding localized time log |
||||
Background: |
||||
Given the following languages are active: |
||||
| en | |
||||
| de | |
||||
And there is 1 user with: |
||||
| login | manager | |
||||
| firstname | the | |
||||
| lastname | manager | |
||||
| language | de | |
||||
And there is 1 project with the following: |
||||
| identifier | ecookbook | |
||||
| name | ecookbook | |
||||
And there is a role "manager" |
||||
And the role "manager" may have the following rights: |
||||
| edit_work_packages | |
||||
| view_work_packages | |
||||
| log_time | |
||||
And I am working in project "ecookbook" |
||||
And the project uses the following modules: |
||||
| time_tracking | |
||||
And the user "manager" is a "manager" |
||||
And there are the following status: |
||||
| name | default | |
||||
| status1 | true | |
||||
And there are the following work packages in project "ecookbook": |
||||
| subject | status_id | |
||||
| pe1 | 1 | |
||||
And there is an activity "design" |
||||
And I am already logged in as "manager" |
||||
|
||||
@javascript |
||||
Scenario: Adding a localized time entry with a too long topic |
||||
Given I am on the edit page of the work package called "pe1" |
||||
When I fill in the following: |
||||
| Thema | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit | |
||||
| Aufgewendete Zeit | 2,5 | |
||||
| Aktivität | design | |
||||
And I submit the form by the "OK" button |
||||
Then I should be on the page of the work package "pe1" |
||||
And I should see 1 error message |
||||
And the "work_package_time_entry_hours" field should contain "2,5" |
@ -1,64 +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. |
||||
#++ |
||||
# |
||||
Feature: Logging time on work package update |
||||
Background: |
||||
Given there is 1 user with: |
||||
| login | manager | |
||||
| firstname | the | |
||||
| lastname | manager | |
||||
And there is 1 project with the following: |
||||
| identifier | ecookbook | |
||||
| name | ecookbook | |
||||
And there is a role "manager" |
||||
And the role "manager" may have the following rights: |
||||
| edit_work_packages | |
||||
| view_work_packages | |
||||
| log_time | |
||||
| view_time_entries | |
||||
And I am working in project "ecookbook" |
||||
And the user "manager" is a "manager" |
||||
And there are the following status: |
||||
| name | default | |
||||
| status1 | true | |
||||
And there are the following work packages in project "ecookbook": |
||||
| subject | status_id | |
||||
| pe1 | 1 | |
||||
And there is an activity "design" |
||||
And I am already logged in as "manager" |
||||
|
||||
@javascript |
||||
Scenario: Logging time |
||||
When I go to the edit page of the work package called "pe1" |
||||
And I fill in the following: |
||||
| Spent time | 5 | |
||||
| Activity | design | |
||||
| Comment | Needed it | |
||||
And I submit the form by the "Submit" button |
||||
Then the work package should be shown with the following values: |
||||
| Spent time | 5.00 | |
@ -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; |
||||
} |
||||
}; |
||||
}; |
@ -0,0 +1,64 @@ |
||||
// -- 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'); |
||||
|
||||
angular.module('openproject.api') |
||||
.run(run) |
||||
.factory('HALAPIResource', HALAPIResource); |
||||
|
||||
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('_', _); |
||||
} |
||||
run.$inject = ['$http', '$q']; |
||||
|
||||
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; |
||||
} |
||||
}; |
||||
} |
@ -1,6 +1,6 @@ |
||||
<div class="inplace-edit--write edit-strategy-{{ strategy }}" ng-show="fieldController.isEditing"> |
||||
<form class="inplace-edit--form" ng-if="fieldController.isEditing" name="editPaneController.editForm" ng-submit="editPaneController.submit()" novalidate> |
||||
<div class="inplace-edit--write-value" ng-include="templateUrl" ng-click="editPaneController.markActive()" tabindex="-1" ng-attr-role="{{ strategy == 'text' || 'wiki_textarea' ? 'textbox' : 'button' }}"> |
||||
<div class="inplace-edit--write-value" ng-include="templateUrl" tabindex="-1" ng-attr-role="{{ strategy == 'text' || 'wiki_textarea' ? 'textbox' : 'button' }}"> |
||||
</div> |
||||
<div class="inplace-edit--dashboard"> |
||||
<div class="inplace-edit--controls" ng-hide="fieldController.state.isBusy || !editPaneController.isActive()"> |
@ -0,0 +1,286 @@ |
||||
// -- 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.
|
||||
// ++
|
||||
|
||||
angular |
||||
.module('openproject.inplace-edit') |
||||
.directive('inplaceEditorEditPane', inplaceEditorEditPane); |
||||
|
||||
function inplaceEditorEditPane(EditableFieldsState, FocusHelper, $timeout, $q) { |
||||
return { |
||||
transclude: true, |
||||
replace: true, |
||||
require: '^workPackageField', |
||||
templateUrl: '/components/inplace-edit/directives/edit-pane/edit-pane.directive.html', |
||||
controllerAs: 'editPaneController', |
||||
controller: InplaceEditorEditPaneController, |
||||
link: function(scope, element, attrs, fieldController) { |
||||
var field = scope.field; |
||||
|
||||
scope.fieldController = fieldController; |
||||
scope.editableFieldsState = EditableFieldsState; |
||||
|
||||
scope.editPaneController.isRequired = function() { |
||||
return field.isRequired(); |
||||
}; |
||||
|
||||
scope.$on('form.updateRequired', function() { |
||||
var submit = $q.defer(); |
||||
|
||||
scope.editPaneController.updateWorkPackageForm(submit); |
||||
}); |
||||
|
||||
scope.$watchCollection('editableFieldsState.workPackage.form', function(form) { |
||||
var strategy = field.getInplaceEditStrategy(); |
||||
|
||||
if (field.name === 'date' && strategy === 'date') { |
||||
form.pendingChanges = EditableFieldsState.getPendingFormChanges(); |
||||
form.pendingChanges['startDate'] = |
||||
form.pendingChanges['dueDate'] = |
||||
field.value ? field.value['dueDate'] : null; |
||||
} |
||||
|
||||
if (strategy !== scope.strategy) { |
||||
scope.strategy = strategy; |
||||
scope.templateUrl = '/templates/inplace-edit/edit/fields/' + |
||||
scope.strategy + '.html'; |
||||
} |
||||
}); |
||||
|
||||
scope.focusInput = function() { |
||||
$timeout(function() { |
||||
var inputElement = element.find('.focus-input'); |
||||
FocusHelper.focus(inputElement); |
||||
inputElement.triggerHandler('keyup'); |
||||
scope.editPaneController.markActive(); |
||||
inputElement.off('focus.inplace').on('focus.inplace', function() { |
||||
// ♥♥♥ angular ♥♥♥
|
||||
scope.$apply(function() { |
||||
scope.editPaneController.markActive(); |
||||
}); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
if (!EditableFieldsState.forcedEditState) { |
||||
element.bind('keydown keypress', function(e) { |
||||
if (e.keyCode === 27 && !EditableFieldsState.editAll.state) { |
||||
scope.$apply(function() { |
||||
scope.editPaneController.discardEditing(); |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
scope.$watch('field.value', function(value) { |
||||
if (scope.fieldController.isEditing) { |
||||
var pendingChanges = EditableFieldsState.getPendingFormChanges(); |
||||
pendingChanges[field.name] = value; |
||||
scope.editPaneController.markActive(); |
||||
} |
||||
}, true); |
||||
scope.$on('workPackageRefreshed', function() { |
||||
scope.editPaneController.discardEditing(); |
||||
}); |
||||
|
||||
scope.$watch('fieldController.isEditing', function(isEditing) { |
||||
var efs = EditableFieldsState; |
||||
|
||||
if (isEditing && !efs.editAll.state && !efs.forcedEditState) { |
||||
scope.focusInput(); |
||||
|
||||
} else if (efs.editAll.state && efs.editAll.isFocusField(field.name)) { |
||||
$timeout(function () { |
||||
var focusElement = element.find('.focus-input'); |
||||
focusElement.length && focusElement.focus()[0].select(); |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
}; |
||||
} |
||||
inplaceEditorEditPane.$inject = ['EditableFieldsState', 'FocusHelper', '$timeout', '$q']; |
||||
|
||||
|
||||
function InplaceEditorEditPaneController($scope, $element, $location, $timeout, $q, $rootScope, |
||||
WorkPackageService, EditableFieldsState, ApiHelper, NotificationsService) { |
||||
|
||||
var showErrors = function() { |
||||
var errors = EditableFieldsState.errors; |
||||
if (_.isEmpty(_.keys(errors))) { |
||||
return; |
||||
} |
||||
var errorMessages = _.flatten(_.map(errors), true); |
||||
NotificationsService.addError(I18n.t('js.label_validation_error'), errorMessages); |
||||
}; |
||||
|
||||
var vm = this; |
||||
var field = $scope.field; |
||||
|
||||
// go full retard
|
||||
var uploadPendingAttachments = function(wp) { |
||||
$rootScope.$broadcast('uploadPendingAttachments', wp); |
||||
}; |
||||
|
||||
// Propagate submission to all active fields
|
||||
// not contained in the workPackage.form (e.g., comment)
|
||||
this.submit = function() { |
||||
EditableFieldsState.save(function() { |
||||
// Clears the location hash, as we're now
|
||||
// scrolling to somewhere else
|
||||
$location.hash(null); |
||||
$timeout(function() { |
||||
$element[0].scrollIntoView(false); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
this.handleFailure = function(e, submit) { |
||||
setFailure(e); |
||||
submit.reject(e); |
||||
}; |
||||
|
||||
this.updateWorkPackageForm = function(submit) { |
||||
WorkPackageService.loadWorkPackageForm(EditableFieldsState.workPackage).then( |
||||
function(form) { |
||||
field.resource.form = form; |
||||
EditableFieldsState.workPackage.form = form; |
||||
if (_.isEmpty(form.embedded.validationErrors.props)) { |
||||
submit.resolve(); |
||||
} else { |
||||
afterError(); |
||||
submit.reject(); |
||||
EditableFieldsState.errors = {}; |
||||
_.forEach(form.embedded.validationErrors.props, function(error, field) { |
||||
if(field === 'startDate' || field === 'dueDate') { |
||||
EditableFieldsState.errors['date'] = error.message; |
||||
} else { |
||||
EditableFieldsState.errors[field] = error.message; |
||||
} |
||||
}); |
||||
|
||||
showErrors(); |
||||
} |
||||
}).catch(function(e) { |
||||
vm.handleFailure(e, submit); |
||||
}); |
||||
|
||||
return submit.promise; |
||||
}; |
||||
|
||||
this.submitField = function() { |
||||
var submit = $q.defer(); |
||||
var fieldController = $scope.fieldController; |
||||
var pendingFormChanges = EditableFieldsState.getPendingFormChanges(); |
||||
var detectedViolations = []; |
||||
|
||||
pendingFormChanges[field.name] = field.value; |
||||
if (vm.editForm.$invalid) { |
||||
var acknowledgedValidationErrors = Object.keys(vm.editForm.$error); |
||||
acknowledgedValidationErrors.forEach(function(error) { |
||||
if (vm.editForm.$error[error]) { |
||||
detectedViolations.push(I18n.t('js.inplace.errors.' + error, { |
||||
field: field.getLabel() |
||||
})); |
||||
} |
||||
}); |
||||
submit.reject(); |
||||
} |
||||
if (detectedViolations.length) { |
||||
EditableFieldsState.errors = EditableFieldsState.errors || {}; |
||||
EditableFieldsState.errors[field.name] = detectedViolations.join(' '); |
||||
showErrors(); |
||||
submit.reject(); |
||||
} else { |
||||
fieldController.state.isBusy = true; |
||||
vm.updateWorkPackageForm(submit).then(function() { |
||||
var result = WorkPackageService.updateWorkPackage( |
||||
EditableFieldsState.workPackage |
||||
); |
||||
result.then(angular.bind(this, function(updatedWorkPackage) { |
||||
submit.resolve(); |
||||
field.resource = _.extend(field.resource, updatedWorkPackage); |
||||
|
||||
$scope.$emit('workPackageUpdatedInEditor', updatedWorkPackage); |
||||
$scope.$on('workPackageRefreshed', function() { |
||||
fieldController.state.isBusy = false; |
||||
fieldController.isEditing = false; |
||||
}); |
||||
uploadPendingAttachments(updatedWorkPackage); |
||||
})).catch(function(e) { |
||||
vm.handleFailure(e, submit); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
return submit.promise; |
||||
}; |
||||
|
||||
this.discardEditing = function() { |
||||
$scope.fieldController.isEditing = false; |
||||
delete EditableFieldsState.submissionPromises['work_package']; |
||||
delete EditableFieldsState.getPendingFormChanges()[field.name]; |
||||
if ( |
||||
EditableFieldsState.errors && |
||||
EditableFieldsState.errors.hasOwnProperty(field.name) |
||||
) { |
||||
delete EditableFieldsState.errors[field.name]; |
||||
} |
||||
}; |
||||
|
||||
this.isActive = function() { |
||||
return EditableFieldsState.isActiveField(field.name); |
||||
}; |
||||
|
||||
this.markActive = function() { |
||||
EditableFieldsState.submissionPromises['work_package'] = { |
||||
field: field.name, |
||||
thePromise: this.submitField, |
||||
prepend: true |
||||
}; |
||||
EditableFieldsState.currentField = field.name; |
||||
}; |
||||
|
||||
function afterError() { |
||||
$scope.fieldController.state.isBusy = false; |
||||
$scope.focusInput(); |
||||
} |
||||
function setFailure(e) { |
||||
afterError(); |
||||
EditableFieldsState.errors = { |
||||
'_common': ApiHelper.getErrorMessages(e) |
||||
}; |
||||
showErrors(); |
||||
} |
||||
|
||||
$scope.$watch('editableFieldsState.editAll.state', function(state) { |
||||
$scope.fieldController.isEditing = state; |
||||
$scope.fieldController.lockFocus = true; |
||||
}); |
||||
} |
||||
InplaceEditorEditPaneController.$inject = ['$scope', '$element', '$location', '$timeout', '$q', |
||||
'$rootScope', 'WorkPackageService', 'EditableFieldsState', 'ApiHelper', 'NotificationsService']; |
@ -1,13 +1,13 @@ |
||||
<div class="spent-time-wrapper"> |
||||
<span ng-if="fieldController.isEmpty()">{{ displayPaneController.placeholder }}</span> |
||||
<span ng-if="!fieldController.isEmpty()"> |
||||
<span ng-if="field.isEmpty()">{{ field.placeholder }}</span> |
||||
<span ng-if="!field.isEmpty()"> |
||||
<span ng-if="customEditorController.isLinkViewable()"> |
||||
<a href="{{ customEditorController.getPath() }}"> |
||||
{{ displayPaneController.getReadValue() }} |
||||
{{ field.text }} |
||||
</a> |
||||
</span> |
||||
<span ng-if="!customEditorController.isLinkViewable()"> |
||||
{{ displayPaneController.getReadValue() }} |
||||
{{ field.text }} |
||||
</span> |
||||
</span> |
||||
</div> |
@ -0,0 +1,28 @@ |
||||
<span class="user-avatar--container"> |
||||
<img class="user-avatar--avatar" |
||||
ng-if="user && user.avatar" |
||||
ng-src="{{user.avatar}}" |
||||
alt="Avatar" |
||||
title="{{user.name}}" /> |
||||
<span class="user-avatar--user-with-role"> |
||||
<span class="user-avatar--user"> |
||||
<a ng-if="!user.isGroup" |
||||
ng-href="{{user.href}}" |
||||
class="user-field-user-link"> |
||||
{{user.name}} |
||||
</a> |
||||
<span ng-if="user.isGroup" |
||||
class="user-field-user-link"> |
||||
{{user.name}} |
||||
</span> |
||||
</span> |
||||
<span class="user-avatar--user" |
||||
ng-if="!user"> |
||||
{{ field.placeholder }} |
||||
</span> |
||||
<span class="user-avatar--role" |
||||
ng-if="user.role"> |
||||
{{user.role}} |
||||
</span> |
||||
</span> |
||||
</span> |
@ -0,0 +1,93 @@ |
||||
// -- 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.
|
||||
// ++
|
||||
|
||||
angular |
||||
.module('openproject.inplace-edit') |
||||
.directive('inplaceDisplayUser', inplaceDisplayUser); |
||||
|
||||
function inplaceDisplayUser() { |
||||
return { |
||||
restrict: 'E', |
||||
transclude: true, |
||||
replace: true, |
||||
require: '^inplaceEditorDisplayPane', |
||||
templateUrl: '/components/inplace-edit/directives/field-display/display-user/' + |
||||
'display-user.directive.html', |
||||
|
||||
controller: InplaceDisplayUserController, |
||||
controllerAs: 'customEditorController', |
||||
|
||||
link: function(scope, element, attrs, inplaceEditorDisplayPane) { |
||||
scope.inplaceEditorDisplayPane = inplaceEditorDisplayPane; |
||||
|
||||
scope.$watch('field.text', function(value) { |
||||
scope.customEditorController.initializeUserWith(value); |
||||
}); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
function InplaceDisplayUserController($scope, PathHelper) { |
||||
var getUserName = function(user) { |
||||
if (user && user.props) { |
||||
return user.props.name; |
||||
} |
||||
}; |
||||
|
||||
var getIsGroup = function(user) { |
||||
return user.props.subtype === 'Group'; |
||||
}; |
||||
|
||||
var getHref = function(user) { |
||||
var id = user.props.id; |
||||
|
||||
return PathHelper.staticUserPath(id); |
||||
}; |
||||
|
||||
var getAvatar = function(user) { |
||||
return user.props.avatar; |
||||
}; |
||||
|
||||
var getRole = function(userData) { |
||||
return userData.props.role; |
||||
}; |
||||
|
||||
this.initializeUserWith = function(userData) { |
||||
$scope.user = userData; |
||||
|
||||
if (userData) { |
||||
$scope.user.name = getUserName(userData); |
||||
$scope.user.isGroup = getIsGroup(userData); |
||||
$scope.user.href = getHref(userData); |
||||
$scope.user.avatar = getAvatar(userData); |
||||
$scope.user.role = getRole(userData); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
InplaceDisplayUserController.$inject = ['$scope', 'PathHelper']; |
@ -0,0 +1,13 @@ |
||||
<div class="version-wrapper"> |
||||
<span ng-if="!field.text"> |
||||
{{field.placeholder}} |
||||
</span> |
||||
<span ng-if="field.text && customEditorController.isVersionLinkViewable()"> |
||||
<a ng-href="{{ customEditorController.versionLink }}"> |
||||
{{field.text.props.name}} |
||||
</a> |
||||
</span> |
||||
<span ng-if="field.text && !customEditorController.isVersionLinkViewable()"> |
||||
{{field.text.props.name}} |
||||
</span> |
||||
</div> |
@ -1,12 +1,24 @@ |
||||
<div class="inplace-edit--date-range"> |
||||
<label |
||||
class="hidden-for-sighted" |
||||
for="inplace-edit--write-value--{{::field.name}}-start"> |
||||
{{::startDateLabel}} |
||||
</label> |
||||
<input type="text" |
||||
class="inplace-edit--date-range-start-date" |
||||
id="inplace-edit--write-value--{{::field.name}}-start" |
||||
ng-change="onStartEdit()" |
||||
ng-model="startDate" ng-class="{'inplace-edit--highlight': startDateIsChanged}" /> |
||||
<div class="inplace-edit--date-range-start-date-picker"></div> |
||||
<span class="delimeter">-</span> |
||||
<label |
||||
class="hidden-for-sighted" |
||||
for="inplace-edit--write-value--{{::field.name}}-end"> |
||||
{{::endDateLabel}} |
||||
</label> |
||||
<input type="text" |
||||
class="inplace-edit--date-range-end-date" |
||||
id="inplace-edit--write-value--{{::field.name}}-end" |
||||
ng-change="onEndEdit()" |
||||
ng-model="endDate" ng-class="{'inplace-edit--highlight': endDateIsChanged}" /> |
||||
<div class="inplace-edit--date-range-end-date-picker"></div> |
@ -0,0 +1,15 @@ |
||||
<div class="inplace-edit--date"> |
||||
<label |
||||
class="hidden-for-sighted" |
||||
for="inplace-edit--write-value--{{::field.name}}"> |
||||
{{::field.getLabel()}} |
||||
</label> |
||||
<input ng-model="field.value" |
||||
ng-change="onEdit()" |
||||
ng-click="showDatepicker()" |
||||
title="{{ fieldController.editTitle }}" |
||||
class="inplace-edit--date" |
||||
id="inplace-edit--write-value--{{::field.name}}" |
||||
type="text" /> |
||||
<div class="inplace-edit--date-picker"></div> |
||||
</div> |
@ -0,0 +1,15 @@ |
||||
<div class="dropdown-wrapper"> |
||||
<label |
||||
class="hidden-for-sighted" |
||||
for="inplace-edit--write-value--{{::field.name}}"> |
||||
{{::field.getLabel()}} |
||||
</label> |
||||
<select |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-model="field.value.props" |
||||
title="{{ fieldController.editTitle }}" |
||||
class="inplace-edit-select" |
||||
id="inplace-edit--write-value--{{::field.name}}" |
||||
ng-options="option as option.name for option in customEditorController.allowedValues track by option.hrefTracker"> |
||||
</select> |
||||
</div> |
@ -0,0 +1,125 @@ |
||||
// -- 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.
|
||||
// ++
|
||||
|
||||
angular |
||||
.module('openproject.inplace-edit') |
||||
.directive('inplaceEditorDropDown', inplaceEditorDropDown); |
||||
|
||||
function inplaceEditorDropDown(EditableFieldsState, FocusHelper) { |
||||
return { |
||||
restrict: 'E', |
||||
transclude: true, |
||||
replace: true, |
||||
require: '^workPackageField', |
||||
templateUrl: '/components/inplace-edit/directives/field-edit/edit-drop-down/' + |
||||
'edit-drop-down.directive.html', |
||||
|
||||
controller: InplaceEditorDropDownController, |
||||
controllerAs: 'customEditorController', |
||||
|
||||
link: function(scope, element, attrs, fieldController) { |
||||
var field = scope.field; |
||||
|
||||
fieldController.state.isBusy = true; |
||||
|
||||
scope.emptyField = !scope.field.isRequired(); |
||||
|
||||
scope.customEditorController.updateAllowedValues(field.name).then(function() { |
||||
fieldController.state.isBusy = false; |
||||
|
||||
if (!EditableFieldsState.forcedEditState) { |
||||
EditableFieldsState.editAll.state || FocusHelper.focusUiSelect(element); |
||||
} |
||||
}); |
||||
} |
||||
}; |
||||
} |
||||
inplaceEditorDropDown.$inject = ['EditableFieldsState', 'FocusHelper']; |
||||
|
||||
function InplaceEditorDropDownController($q, $scope, I18n, WorkPackageFieldConfigurationService) { |
||||
|
||||
this.allowedValues = []; |
||||
|
||||
this.updateAllowedValues = function(field) { |
||||
var customEditorController = this; |
||||
|
||||
return $q(function(resolve) { |
||||
$scope.field.getAllowedValues() |
||||
.then(function(values) { |
||||
|
||||
var sorting = WorkPackageFieldConfigurationService |
||||
.getDropdownSortingStrategy(field); |
||||
|
||||
if (sorting !== null) { |
||||
values = _.sortBy(values, sorting); |
||||
} |
||||
|
||||
if (!$scope.field.isRequired()) { |
||||
values = addEmptyOption(values); |
||||
} |
||||
|
||||
addHrefTracker(values); |
||||
|
||||
customEditorController.allowedValues = values; |
||||
|
||||
resolve(); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
var addEmptyOption = function(values) { |
||||
var emptyOption = { props: { href: null, |
||||
name: $scope.field.placeholder } }; |
||||
|
||||
if (!$scope.field.isRequired()) { |
||||
var arrayWithEmptyOption = [emptyOption.props]; |
||||
|
||||
values = arrayWithEmptyOption.concat(values); |
||||
|
||||
if ($scope.field.value === null) { |
||||
$scope.field.value = emptyOption; |
||||
} |
||||
} |
||||
|
||||
return values; |
||||
}; |
||||
|
||||
// We have to maintain a separate property just to track the object by
|
||||
// in the template. This is due to angular aparently not being able to
|
||||
// track correclty with a field having null as it's value. It does work for
|
||||
// 'null' (String) however.
|
||||
var addHrefTracker = function(values) { |
||||
_.forEach(values, function(value) { |
||||
value.hrefTracker = String(value.href); |
||||
}); |
||||
|
||||
$scope.field.value.props.hrefTracker = String($scope.field.value.props.href); |
||||
}; |
||||
} |
||||
InplaceEditorDropDownController.$inject = ['$q', '$scope', 'I18n', |
||||
'WorkPackageFieldConfigurationService']; |
@ -0,0 +1,16 @@ |
||||
<div class="edit-duration-wrapper"> |
||||
<label |
||||
class="hidden-for-sighted" |
||||
for="inplace-edit--write-value--{{::field.name}}"> |
||||
{{::field.getLabel()}} |
||||
</label> |
||||
<input class="focus-input inplace-edit--text-field" |
||||
id="inplace-edit--write-value--{{::field.name}}" |
||||
name="value" |
||||
type="number" |
||||
step="0.5" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-required="fieldController.isRequired" |
||||
title="{{ fieldController.editTitle }}" |
||||
ng-model="numberValue" /> |
||||
</div> |
@ -0,0 +1,3 @@ |
||||
<div class="type-wrapper"> |
||||
<inplace-editor-drop-down></inplace-editor-drop-down> |
||||
</div> |
@ -0,0 +1,24 @@ |
||||
<div class="textarea-wrapper" ng-class="{'-preview': customEditorController.isPreview}"> |
||||
<label |
||||
class="hidden-for-sighted" |
||||
for="inplace-edit--write-value--{{::field.name}}"> |
||||
{{::field.getLabel()}} |
||||
</label> |
||||
<textarea |
||||
wiki-toolbar |
||||
style="min-height: 38px" |
||||
msd-elastic="\n" |
||||
class="focus-input inplace-edit--textarea -animated" |
||||
id="inplace-edit--write-value--{{::field.name}}" |
||||
ng-hide="customEditorController.isPreview && !fieldController.state.isBusy" |
||||
preview-toggle="customEditorController.togglePreview()" |
||||
name="value" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-required="fieldController.isRequired" |
||||
ng-model="field.value.raw" |
||||
title="{{ fieldController.editTitle }}"> |
||||
</textarea> |
||||
<div class="inplace-edit--preview" ng-if="customEditorController.isPreview && !fieldController.state.isBusy"> |
||||
<span ng-bind-html="customEditorController.previewHtml"></span> |
||||
</div> |
||||
</div> |
@ -1,5 +1,5 @@ |
||||
<div |
||||
class="inplace-edit attribute-{{ fieldController.field }}" |
||||
class="inplace-edit attribute-{{ field.name }}" |
||||
ng-class="{'-busy': fieldController.state.isBusy}" |
||||
aria-busy="{{ fieldController.state.isBusy }}"> |
||||
<ng-transclude></ng-transclude> |
@ -0,0 +1,7 @@ |
||||
<div id="work-package-{{ field.name }}" |
||||
class="work-package-field work-packages--details--{{ field.name }}"> |
||||
<inplace-editor-main-pane> |
||||
<inplace-editor-display-pane></inplace-editor-display-pane> |
||||
<inplace-editor-edit-pane ng-if="field.isEditable()"></inplace-editor-edit-pane> |
||||
</inplace-editor-main-pane> |
||||
</div> |
@ -0,0 +1,63 @@ |
||||
//-- 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.
|
||||
//++
|
||||
|
||||
angular |
||||
.module('openproject.inplace-edit') |
||||
.directive('workPackageField', workPackageField); |
||||
|
||||
function workPackageField() { |
||||
return { |
||||
restrict: 'E', |
||||
replace: true, |
||||
templateUrl: '/components/inplace-edit/directives/work-package-field/' + |
||||
'work-package-field.directive.html', |
||||
scope: { |
||||
fieldName: '=' |
||||
}, |
||||
|
||||
bindToController: true, |
||||
controller: WorkPackageFieldController, |
||||
controllerAs: 'fieldController' |
||||
}; |
||||
} |
||||
|
||||
function WorkPackageFieldController($scope, EditableFieldsState, inplaceEdit) { |
||||
var workPackage = EditableFieldsState.workPackage; |
||||
this.state = EditableFieldsState; |
||||
$scope.field = inplaceEdit.form(workPackage.props.id, workPackage).field(this.fieldName); |
||||
|
||||
var field = $scope.field; |
||||
|
||||
if (field.isEditable()) { |
||||
this.state.isBusy = false; |
||||
this.isEditing = this.state.forcedEditState; |
||||
this.editTitle = I18n.t('js.inplace.button_edit', { attribute: field.getLabel() }); |
||||
} |
||||
} |
||||
WorkPackageFieldController.$inject = ['$scope', 'EditableFieldsState', 'inplaceEdit']; |
||||
|
@ -0,0 +1,113 @@ |
||||
// -- 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.
|
||||
// ++
|
||||
|
||||
angular |
||||
.module('openproject.inplace-edit') |
||||
.factory('inplaceEdit', inplaceEdit); |
||||
|
||||
function inplaceEdit(WorkPackageFieldService) { |
||||
var forms = {}; |
||||
|
||||
function Form(resource) { |
||||
this.resource = resource; |
||||
this.fields = {}; |
||||
|
||||
this.field = function (name) { |
||||
this.fields[name] = this.fields[name] || new Field(this.resource, name); |
||||
|
||||
return this.fields[name]; |
||||
}; |
||||
} |
||||
|
||||
Object.defineProperty(Form.prototype, 'length', { |
||||
get: function () { |
||||
return Object.keys(this.fields).length; |
||||
} |
||||
}); |
||||
|
||||
function Field(resource, name) { |
||||
this.resource = resource; |
||||
this.name = name; |
||||
this.value = !_.isUndefined(this.value) ? this.value : _.cloneDeep(this.getValue()); |
||||
} |
||||
|
||||
Object.defineProperty(Field.prototype, 'text', { |
||||
get: function() { |
||||
return this.format(); |
||||
} |
||||
}); |
||||
|
||||
// Looks up placeholders in the localization files (e.g. js-en.yml).
|
||||
// The path is
|
||||
// js:
|
||||
// [name of the resource in snake case and pluralized]:
|
||||
// placeholders:
|
||||
// [name of the field]:
|
||||
//
|
||||
// Falls back to default if no specific placeholder is defined.
|
||||
Object.defineProperty(Field.prototype, 'placeholder', { |
||||
get: function() { |
||||
|
||||
if (this.resource.props._type === undefined) { |
||||
return I18n.t('js.placeholders.default'); |
||||
} |
||||
|
||||
// lodash does snakeCase in version 3.10
|
||||
// This also pluralizes the easy way by appending 's' to the end
|
||||
// which is error prone
|
||||
var resourceName = this.resource.props._type |
||||
.replace(/([A-Z])/g, function($1){return '_' + $1.toLowerCase();}) |
||||
.replace(/^_/, '') + 's'; |
||||
|
||||
var scope = 'js.' + resourceName + '.placeholders.' + this.name; |
||||
|
||||
var translation = I18n.t(scope); |
||||
if (I18n.missingTranslation(scope) === translation) { |
||||
return I18n.t('js.' + resourceName + '.placeholders.default'); |
||||
} |
||||
else { |
||||
return translation; |
||||
} |
||||
} |
||||
}); |
||||
|
||||
_.forOwn(WorkPackageFieldService, function (property, name) { |
||||
Field.prototype[name] = _.isFunction(property) && function () { |
||||
return property(this.resource, this.name); |
||||
} || property; |
||||
}); |
||||
|
||||
return { |
||||
form: function (id, resource) { |
||||
forms[id] = forms[id] || new Form(resource); |
||||
|
||||
return forms[id]; |
||||
} |
||||
}; |
||||
} |
||||
inplaceEdit.$inject = ['WorkPackageFieldService']; |
@ -1 +0,0 @@ |
||||
<span ng-bind="displayPaneController.getReadValue()"></span> |
@ -1 +0,0 @@ |
||||
<op-date date-value="displayPaneController.getReadValue()" no-date-text="displayPaneController.placeholder"></op-date> |
@ -1 +0,0 @@ |
||||
<op-date date-value="displayPaneController.getReadValue().startDate" no-date-text="displayPaneController.getReadValue().noStartDate"></op-date> - <op-date date-value="displayPaneController.getReadValue().dueDate" no-date-text="displayPaneController.getReadValue().noEndDate"></op-date> |
@ -1 +0,0 @@ |
||||
<span ng-bind="displayPaneController.getReadValue().props.name || displayPaneController.getReadValue().props.value || displayPaneController.placeholder"></span> |
@ -1,2 +0,0 @@ |
||||
<span ng-if="displayPaneController.isReadValueEmpty()" ng-bind="displayPaneController.placeholder"></span> |
||||
<span ng-if="!displayPaneController.isReadValueEmpty()" ng-bind="displayPaneController.getReadValue()"></span> |
@ -1 +0,0 @@ |
||||
<span ng-bind-html="displayPaneController.getReadValue().html || displayPaneController.placeholder"></span> |
@ -1,10 +0,0 @@ |
||||
<div class="switch"> |
||||
<input type="checkbox" |
||||
class="focus-input" |
||||
id="checkbox-switch-{{ fieldController.field }}" |
||||
name="value" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
title="{{ fieldController.editTitle }}" |
||||
ng-model="fieldController.writeValue" /> |
||||
<label for="checkbox-switch-{{ fieldController.field }}" title="{{ fieldController.editTitle }}"></label> |
||||
</div> |
@ -1 +0,0 @@ |
||||
<inplace-editor-dropdown></inplace-editor-dropdown> |
@ -1,8 +0,0 @@ |
||||
<input class="focus-input inplace-edit--text-field" |
||||
name="value" |
||||
type="number" |
||||
step="0.01" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-required="fieldController.isRequired" |
||||
title="{{ fieldController.editTitle }}" |
||||
ng-model="fieldController.writeValue" /> |
@ -1,7 +0,0 @@ |
||||
<input class="focus-input inplace-edit--text-field" |
||||
name="value" |
||||
type="number" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-required="fieldController.isRequired" |
||||
title="{{ fieldController.editTitle }}" |
||||
ng-model="fieldController.writeValue" /> |
@ -1,7 +0,0 @@ |
||||
<input class="focus-input inplace-edit--text-field" |
||||
name="value" |
||||
type="text" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-required="fieldController.isRequired" |
||||
title="{{ fieldController.editTitle }}" |
||||
ng-model="fieldController.writeValue" /> |
@ -0,0 +1 @@ |
||||
<span ng-bind="field.text"></span> |
@ -0,0 +1 @@ |
||||
<op-date date-value="field.text.startDate" no-date-text="field.text.noStartDate"></op-date> - <op-date date-value="field.text.dueDate" no-date-text="field.text.noEndDate"></op-date> |
@ -0,0 +1 @@ |
||||
<op-date date-value="field.text" no-date-text="field.placeholder"></op-date> |
@ -1,5 +1,5 @@ |
||||
<work-package-dynamic-attribute |
||||
html-element="displayPaneController.getDynamicDirectiveName()" |
||||
work-package="displayPaneController.getWorkPackage()" |
||||
field="field" |
||||
class="dynamic-attribute"> |
||||
</work-package-dynamic-attribute> |
@ -0,0 +1 @@ |
||||
<span ng-bind="field.text.props.name || field.text.props.value || field.placeholder"></span> |
@ -0,0 +1,2 @@ |
||||
<span ng-if="field.isEmpty()" ng-bind="field.placeholder"></span> |
||||
<span ng-if="!field.isEmpty()" ng-bind="field.text"></span> |
@ -0,0 +1 @@ |
||||
<span ng-bind-html="field.text.html || field.placeholder"></span> |
@ -0,0 +1,14 @@ |
||||
<div class="switch"> |
||||
<label |
||||
class="hidden-for-sighted" |
||||
for="inplace-edit--write-value--{{::field.name}}"> |
||||
{{::field.getLabel()}} |
||||
</label> |
||||
<input type="checkbox" |
||||
class="focus-input" |
||||
id="inplace-edit--write-value--{{::field.name}}" |
||||
name="value" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
title="{{ fieldController.editTitle }}" |
||||
ng-model="field.value" /> |
||||
</div> |
@ -0,0 +1 @@ |
||||
<inplace-editor-drop-down></inplace-editor-drop-down> |
@ -0,0 +1,16 @@ |
||||
<div class="float-wrapper"> |
||||
<label |
||||
class="hidden-for-sighted" |
||||
for="inplace-edit--write-value--{{::field.name}}"> |
||||
{{::field.getLabel()}} |
||||
</label> |
||||
<input class="focus-input inplace-edit--text-field" |
||||
id="inplace-edit--write-value--{{::field.name}}" |
||||
name="value" |
||||
type="number" |
||||
step="0.01" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-required="fieldController.isRequired" |
||||
title="{{ fieldController.editTitle }}" |
||||
ng-model="field.value" /> |
||||
</div> |
@ -0,0 +1,15 @@ |
||||
<div class="integer-wrapper"> |
||||
<label |
||||
class="hidden-for-sighted" |
||||
for="inplace-edit--write-value--{{::field.name}}"> |
||||
{{::field.getLabel()}} |
||||
</label> |
||||
<input class="focus-input inplace-edit--text-field" |
||||
id="inplace-edit--write-value--{{::field.name}}" |
||||
name="value" |
||||
type="number" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-required="fieldController.isRequired" |
||||
title="{{ fieldController.editTitle }}" |
||||
ng-model="field.value" /> |
||||
</div> |
@ -0,0 +1,15 @@ |
||||
<div class="text-wrapper"> |
||||
<label |
||||
class="hidden-for-sighted" |
||||
for="inplace-edit--write-value--{{::field.name}}"> |
||||
{{::field.getLabel()}} |
||||
</label> |
||||
<input class="focus-input inplace-edit--text-field" |
||||
id="inplace-edit--write-value--{{::field.name}}" |
||||
name="value" |
||||
type="text" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-required="fieldController.isRequired" |
||||
title="{{ fieldController.editTitle }}" |
||||
ng-model="field.value" /> |
||||
</div> |
@ -0,0 +1 @@ |
||||
<inplace-editor-type></inplace-editor-type> |
@ -1,7 +0,0 @@ |
||||
<div id="work-package-{{ fieldController.field }}" |
||||
class="work-package-field work-packages--details--{{ fieldController.field }}"> |
||||
<inplace-editor-main-pane> |
||||
<inplace-editor-display-pane></inplace-editor-display-pane> |
||||
<inplace-editor-edit-pane ng-if="fieldController.isEditable()"></inplace-editor-edit-pane> |
||||
</inplace-editor-main-pane> |
||||
</div> |
@ -1,14 +0,0 @@ |
||||
<span class="user-avatar--container"> |
||||
<img class="user-avatar--avatar" |
||||
ng-if="customEditorController.getUserName() && customEditorController.getUser().props.avatar" |
||||
ng-src="{{customEditorController.getUser().props.avatar}}" alt="Avatar" title="{{customEditorController.getUserName()}}" /> |
||||
<span class="user-avatar--user-with-role"> |
||||
<span class="user-avatar--user" ng-if="customEditorController.getUserName()"> |
||||
<a ng-if="customEditorController.getUser().props.subtype !== 'Group'" ng-href="{{ customEditorController.userPath(customEditorController.getUser().props.id) }}" ng-bind="customEditorController.getUser().props.name" |
||||
class="user-field-user-link"/> |
||||
<span ng-if="customEditorController.getUser().props.subtype == 'Group'" ng-bind="customEditorController.getUser().props.name" class="user-field-user-link"/> |
||||
</span> |
||||
<span class="user-avatar--user" ng-if="!customEditorController.getUserName()"> - </span> |
||||
<span class="user-avatar--role" ng-if="customEditorController.getUser().props.role" ng-bind="customEditorController.getUser().props.role"/> |
||||
</span> |
||||
</span> |
@ -1,11 +0,0 @@ |
||||
<div class="version-wrapper"> |
||||
<span ng-if="!displayPaneController.getReadValue()">-</span> |
||||
<span ng-if="displayPaneController.getReadValue() && customEditorController.isVersionLinkViewable()"> |
||||
<a href="{{customEditorController.pathHelper.staticVersionPath(displayPaneController.getReadValue().props.id)}}"> |
||||
{{displayPaneController.getReadValue().props.name}} |
||||
</a> |
||||
</span> |
||||
<span ng-if="displayPaneController.getReadValue() && !customEditorController.isVersionLinkViewable()"> |
||||
{{displayPaneController.getReadValue().props.name}} |
||||
</span> |
||||
</div> |
@ -1,9 +0,0 @@ |
||||
<div class="inplace-edit--date"> |
||||
<input ng-model="fieldController.writeValue" |
||||
ng-change="onEdit()" |
||||
ng-click="showDatepicker()" |
||||
title="{{ fieldController.editTitle }}" |
||||
class="inplace-edit--date" |
||||
type="text" /> |
||||
<div class="inplace-edit--date-picker"></div> |
||||
</div> |
@ -1,16 +0,0 @@ |
||||
<div class="dropdown-wrapper"> |
||||
<ui-select |
||||
class="inplace-edit--select" |
||||
name="value" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-model="fieldController.writeValue.props.href" |
||||
title="{{ fieldController.editTitle }}" |
||||
reset-search-input="true" |
||||
theme="select2"> |
||||
<ui-select-match>{{ $select.selected.name }}</ui-select-match> |
||||
<ui-select-choices |
||||
repeat="item.href as item in customEditorController.allowedValues | filter: $select.search"> |
||||
<div aria-label="{{ item.name || customEditorController.nullValueLabel }}" ng-bind-html="item.name | highlight: $select.search"></div> |
||||
</ui-select-choices> |
||||
</ui-select> |
||||
</div> |
@ -1,8 +0,0 @@ |
||||
<input class="focus-input inplace-edit--text-field" |
||||
name="value" |
||||
type="number" |
||||
step="0.5" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-required="fieldController.isRequired" |
||||
title="{{ fieldController.editTitle }}" |
||||
ng-model="customEditorController.writeValue" /> |
@ -1,18 +0,0 @@ |
||||
<div class="textarea-wrapper" ng-class="{'-preview': customEditorController.isPreview}"> |
||||
<textarea |
||||
wiki-toolbar |
||||
style="min-height: 38px" |
||||
msd-elastic="\n" |
||||
class="focus-input inplace-edit--textarea -animated" |
||||
ng-hide="customEditorController.isPreview && !fieldController.state.isBusy" |
||||
preview-toggle="customEditorController.togglePreview()" |
||||
name="value" |
||||
ng-disabled="fieldController.state.isBusy" |
||||
ng-required="fieldController.isRequired" |
||||
ng-model="fieldController.writeValue.raw" |
||||
title="{{ fieldController.editTitle }}"> |
||||
</textarea> |
||||
<div class="inplace-edit--preview" ng-if="customEditorController.isPreview && !fieldController.state.isBusy"> |
||||
<span ng-bind-html="customEditorController.previewHtml"></span> |
||||
</div> |
||||
</div> |
@ -0,0 +1,10 @@ |
||||
<div class="work-packages--edit-actions" ng-show="efs.editAll.state && efs.editAll.allowed"> |
||||
<button class="button -alt-highlight" accesskey="3" ng-click="efs.save()"> |
||||
<i class="button--icon icon-yes"></i> |
||||
<span class="button--text" ng-bind="::I18n.t('js.button_save')"></span> |
||||
</button> |
||||
<button class="button" accesskey="7" ng-click="efs.editAll.cancel()"> |
||||
<i class="button--icon icon-close"></i> |
||||
<span class="button--text" ng-bind="::I18n.t('js.button_cancel')"></span> |
||||
</button> |
||||
</div> |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue