Conflicts: frontend/app/components/wp-panels/overview-panel/overview-panel.directive.tspull/4338/head
commit
fe8007fa19
@ -0,0 +1,6 @@ |
|||||||
|
#announcement |
||||||
|
margin-left: auto |
||||||
|
margin-right: auto |
||||||
|
margin-top: 1rem |
||||||
|
width: 511px |
||||||
|
|
@ -0,0 +1,30 @@ |
|||||||
|
class AnnouncementsController < ApplicationController |
||||||
|
layout 'admin' |
||||||
|
|
||||||
|
before_filter :require_admin |
||||||
|
|
||||||
|
def edit |
||||||
|
@announcement = Announcement.only_one |
||||||
|
end |
||||||
|
|
||||||
|
def update |
||||||
|
@announcement = Announcement.only_one |
||||||
|
@announcement.attributes = announcement_params |
||||||
|
|
||||||
|
if @announcement.save |
||||||
|
flash[:success] = t(:notice_successful_update) |
||||||
|
end |
||||||
|
|
||||||
|
render action: 'edit' |
||||||
|
end |
||||||
|
|
||||||
|
private |
||||||
|
|
||||||
|
def default_breadcrumb |
||||||
|
t(:label_announcement) |
||||||
|
end |
||||||
|
|
||||||
|
def announcement_params |
||||||
|
params.require(:announcement).permit('text', 'show_until', 'active') |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,9 @@ |
|||||||
|
module AnnouncementsHelper |
||||||
|
def notice_annoucement_active |
||||||
|
if @announcement.active_and_current? |
||||||
|
l(:'announcements.is_active') |
||||||
|
else |
||||||
|
l(:'announcements.is_inactive') |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,26 @@ |
|||||||
|
class Announcement < ActiveRecord::Base |
||||||
|
scope :active, -> { where(active: true) } |
||||||
|
scope :current, -> { where('show_until >= ?', Date.today) } |
||||||
|
|
||||||
|
validates :show_until, presence: true |
||||||
|
|
||||||
|
def self.active_and_current |
||||||
|
active.current.first |
||||||
|
end |
||||||
|
|
||||||
|
def self.only_one |
||||||
|
a = first |
||||||
|
a = create_default_announcement if a.nil? |
||||||
|
a |
||||||
|
end |
||||||
|
|
||||||
|
def active_and_current? |
||||||
|
active? && show_until && show_until >= Date.today |
||||||
|
end |
||||||
|
|
||||||
|
def self.create_default_announcement |
||||||
|
Announcement.create text: 'Announcement', |
||||||
|
show_until: Date.today + 14.days, |
||||||
|
active: false |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,10 @@ |
|||||||
|
<% announcement = Announcement.active_and_current %> |
||||||
|
<% if announcement.present? %> |
||||||
|
<div id="announcement"> |
||||||
|
<div class="notification-box -info"> |
||||||
|
<div class="notification-box--content"> |
||||||
|
<%= format_text announcement.text %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<% end %> |
@ -0,0 +1,22 @@ |
|||||||
|
<% html_title t(:label_administration), t(:label_announcement) %> |
||||||
|
|
||||||
|
<%= error_messages_for 'announcement' %> |
||||||
|
|
||||||
|
<%= toolbar title: "#{t(:label_announcement)} (#{notice_annoucement_active})" %> |
||||||
|
|
||||||
|
<%= labelled_tabular_form_for @announcement, |
||||||
|
:url => {:action => :update}, |
||||||
|
:html => {:method => :put} do |f|%> |
||||||
|
<div class="form--field"> |
||||||
|
<%= f.text_area :text, :cols => 80, :rows => 5, label: t(:label_text) %> |
||||||
|
</div> |
||||||
|
<div class="form--field"> |
||||||
|
<%= f.text_field :show_until, label: t('announcements.show_until') %> |
||||||
|
<%= calendar_for("announcement_show_until") %> |
||||||
|
</div> |
||||||
|
<div class="form--field"> |
||||||
|
<%= f.check_box :active, label: t(:label_active) %> |
||||||
|
</div> |
||||||
|
<hr class="form-separator"> |
||||||
|
<%= styled_button_tag t(:button_save), class: '-highlight -with-icon icon-checkmark' %> |
||||||
|
<% end %> |
@ -0,0 +1,9 @@ |
|||||||
|
coverage: |
||||||
|
ignore: |
||||||
|
- spec/factories/.* |
||||||
|
- vendor/bundle/.* |
||||||
|
status: |
||||||
|
patch: false |
||||||
|
project: |
||||||
|
default: {} |
||||||
|
comment: off |
@ -0,0 +1,28 @@ |
|||||||
|
require Rails.root.join('db', 'migrate', 'migration_utils', 'migration_squasher').to_s |
||||||
|
require 'open_project/plugins/migration_mapping' |
||||||
|
# This migration aggregates the migrations detailed in the MIGRATION_FILES |
||||||
|
class AggregatedAnnouncementsMigrations < ActiveRecord::Migration |
||||||
|
MIGRATION_FILES = <<-MIGRATIONS |
||||||
|
001_create_announcements.rb |
||||||
|
20121114100640_index_on_announcements.rb |
||||||
|
MIGRATIONS |
||||||
|
|
||||||
|
OLD_PLUGIN_NAME = 'redmine_announcements' |
||||||
|
|
||||||
|
def up |
||||||
|
migration_names = OpenProject::Plugins::MigrationMapping.migration_files_to_migration_names(MIGRATION_FILES, OLD_PLUGIN_NAME) |
||||||
|
Migration::MigrationSquasher.squash(migration_names) do |
||||||
|
create_table :announcements do |t| |
||||||
|
t.text :text |
||||||
|
t.date :show_until |
||||||
|
t.boolean :active, default: false |
||||||
|
t.timestamps |
||||||
|
end |
||||||
|
add_index :announcements, [:show_until, :active] |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def down |
||||||
|
drop_table :announcements |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,132 @@ |
|||||||
|
// -- 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.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
function notifications($rootScope) { |
||||||
|
var createNotification = function (message) { |
||||||
|
if (typeof message === 'string') { |
||||||
|
return {message: message}; |
||||||
|
} |
||||||
|
return message; |
||||||
|
}, |
||||||
|
createSuccessNotification = function (message) { |
||||||
|
return _.extend(createNotification(message), {type: 'success'}); |
||||||
|
}, |
||||||
|
createWarningNotification = function (message) { |
||||||
|
return _.extend(createNotification(message), {type: 'warning'}); |
||||||
|
}, |
||||||
|
createErrorNotification = function (message, errors) { |
||||||
|
return _.extend(createNotification(message), { |
||||||
|
type: 'error', |
||||||
|
errors: errors || [] |
||||||
|
}); |
||||||
|
}, |
||||||
|
createNoticeNotification = function (message) { |
||||||
|
return _.extend(createNotification(message), {type: ''}); |
||||||
|
}, |
||||||
|
createWorkPackageUploadNotification = function (message, uploads) { |
||||||
|
if (!uploads) { |
||||||
|
throw new Error('Cannot create an upload notification without uploads!'); |
||||||
|
} |
||||||
|
return _.extend(createNotification(message), { |
||||||
|
type: 'upload', |
||||||
|
uploads: uploads |
||||||
|
}); |
||||||
|
}, |
||||||
|
broadcast = function (event, data) { |
||||||
|
$rootScope.$broadcast(event, data); |
||||||
|
}, |
||||||
|
currentNotifications = [], |
||||||
|
notificationAdded = function (newNotification) { |
||||||
|
var toRemove = currentNotifications.slice(0); |
||||||
|
_.each(toRemove, function (existingNotification) { |
||||||
|
if (newNotification.type === 'success' || newNotification.type === 'error') { |
||||||
|
remove(existingNotification); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
currentNotifications.push(newNotification); |
||||||
|
}, |
||||||
|
notificationRemoved = function (removedNotification) { |
||||||
|
_.remove(currentNotifications, function (element) { |
||||||
|
return element === removedNotification; |
||||||
|
}); |
||||||
|
}, |
||||||
|
clearNotifications = function () { |
||||||
|
currentNotifications.forEach(function (notification) { |
||||||
|
remove(notification); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
$rootScope.$on('notification.remove', function (_e, notification) { |
||||||
|
notificationRemoved(notification); |
||||||
|
}); |
||||||
|
|
||||||
|
$rootScope.$on('notifications.clearAll', function () { |
||||||
|
clearNotifications(); |
||||||
|
}); |
||||||
|
|
||||||
|
// public
|
||||||
|
var add = function (message) { |
||||||
|
var notification = createNotification(message); |
||||||
|
broadcast('notification.add', notification); |
||||||
|
notificationAdded(notification); |
||||||
|
return notification; |
||||||
|
}, |
||||||
|
addError = function (message, errors) { |
||||||
|
return add(createErrorNotification(message, errors)); |
||||||
|
}, |
||||||
|
addWarning = function (message) { |
||||||
|
return add(createWarningNotification(message)); |
||||||
|
}, |
||||||
|
addSuccess = function (message) { |
||||||
|
return add(createSuccessNotification(message)); |
||||||
|
}, |
||||||
|
addNotice = function (message) { |
||||||
|
return add(createNoticeNotification(message)); |
||||||
|
}, |
||||||
|
addWorkPackageUpload = function (message, uploads) { |
||||||
|
return add(createWorkPackageUploadNotification(message, uploads)); |
||||||
|
}, |
||||||
|
remove = function (notification) { |
||||||
|
broadcast('notification.remove', notification); |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
add: add, |
||||||
|
remove: remove, |
||||||
|
addError: addError, |
||||||
|
addWarning: addWarning, |
||||||
|
addSuccess: addSuccess, |
||||||
|
addNotice: addNotice, |
||||||
|
addWorkPackageUpload: addWorkPackageUpload |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
angular |
||||||
|
.module('openproject.services') |
||||||
|
.factory('NotificationsService', notifications); |
@ -0,0 +1,109 @@ |
|||||||
|
// -- 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.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {opServicesModule} from '../../../angular-modules.ts'; |
||||||
|
|
||||||
|
function wpAttachmentsService($q, $timeout, $http, Upload, I18n, NotificationsService) { |
||||||
|
var upload = (workPackage, files) => { |
||||||
|
var uploadPath = workPackage.$links.addAttachment.$link.href; |
||||||
|
var uploads = _.map(files, (file:any) => { |
||||||
|
var options = { |
||||||
|
url: uploadPath, |
||||||
|
fields: { |
||||||
|
metadata: { |
||||||
|
fileName: file.name, |
||||||
|
description: file.description |
||||||
|
} |
||||||
|
}, |
||||||
|
file: file |
||||||
|
}; |
||||||
|
return Upload.upload(options); |
||||||
|
}); |
||||||
|
|
||||||
|
// notify the user
|
||||||
|
var message = I18n.t('js.label_upload_notification', { |
||||||
|
id: workPackage.id, |
||||||
|
subject: workPackage.subject |
||||||
|
}); |
||||||
|
|
||||||
|
var notification = NotificationsService.addWorkPackageUpload(message, uploads); |
||||||
|
var allUploadsDone = $q.defer(); |
||||||
|
$q.all(uploads).then(function () { |
||||||
|
$timeout(function () { // let the notification linger for a bit
|
||||||
|
NotificationsService.remove(notification); |
||||||
|
allUploadsDone.resolve(); |
||||||
|
}, 700); |
||||||
|
}, function (err) { |
||||||
|
allUploadsDone.reject(err); |
||||||
|
}); |
||||||
|
return allUploadsDone.promise; |
||||||
|
}, |
||||||
|
|
||||||
|
load = function (workPackage, reload) { |
||||||
|
var path = workPackage.$links.attachments.$link.href, |
||||||
|
attachments = $q.defer(); |
||||||
|
$http.get(path, {cache: !reload}).success(function (response) { |
||||||
|
attachments.resolve(response._embedded.elements); |
||||||
|
}).error(function (err) { |
||||||
|
attachments.reject(err); |
||||||
|
}); |
||||||
|
return attachments.promise; |
||||||
|
}, |
||||||
|
|
||||||
|
remove = function (fileOrAttachment) { |
||||||
|
var removal = $q.defer(); |
||||||
|
if (angular.isObject(fileOrAttachment._links)) { |
||||||
|
var path = fileOrAttachment._links.self.href; |
||||||
|
$http.delete(path).success(function () { |
||||||
|
removal.resolve(fileOrAttachment); |
||||||
|
}).error(function (err) { |
||||||
|
removal.reject(err); |
||||||
|
}); |
||||||
|
} else { |
||||||
|
removal.resolve(fileOrAttachment); |
||||||
|
} |
||||||
|
return removal.promise; |
||||||
|
}, |
||||||
|
|
||||||
|
hasAttachments = function (workPackage) { |
||||||
|
var existance = $q.defer(); |
||||||
|
load(workPackage).then(function (attachments:any) { |
||||||
|
existance.resolve(attachments.length > 0); |
||||||
|
}); |
||||||
|
return existance.promise; |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
upload: upload, |
||||||
|
remove: remove, |
||||||
|
load: load, |
||||||
|
hasAttachments: hasAttachments |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
opServicesModule.factory('WorkPackageAttachmentsService', wpAttachmentsService); |
@ -1,19 +1,19 @@ |
|||||||
<span class="wp-table--cell-span" |
<span class="wp-table--cell-span" |
||||||
ng-switch="vm.displayType" |
ng-switch="$ctrl.displayType" |
||||||
wp-field="vm.workPackage" |
wp-field="$ctrl.workPackage" |
||||||
field-name="vm.attribute"> |
field-name="$ctrl.attribute"> |
||||||
<progress-bar ng-switch-when="Percent" |
<progress-bar ng-switch-when="Percent" |
||||||
progress="vm.displayText" |
progress="$ctrl.displayText" |
||||||
width="80px"> |
width="80px"> |
||||||
</progress-bar> |
</progress-bar> |
||||||
|
|
||||||
<span ng-switch-when="SelfLink" title="{{ vm.displayText }}"> |
<span ng-switch-when="SelfLink" title="{{ $ctrl.displayText }}"> |
||||||
<a ng-href="{{ vm.displayLink }}">{{ vm.displayText }}</a> |
<a ng-href="{{ $ctrl.displayLink }}">{{ $ctrl.displayText }}</a> |
||||||
</span> |
</span> |
||||||
|
|
||||||
<span ng-switch-default |
<span ng-switch-default |
||||||
title="{{ vm.displayText }}" |
title="{{ $ctrl.displayText }}" |
||||||
ng-class="{ 'work-package--placeholder' : vm.displayText == '-' }"> |
ng-class="{ 'work-package--placeholder' : $ctrl.displayText == '-' }"> |
||||||
{{ vm.displayText }} |
{{ $ctrl.displayText }} |
||||||
</span> |
</span> |
||||||
</span> |
</span> |
||||||
|
@ -0,0 +1,299 @@ |
|||||||
|
// -- 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.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {opWorkPackagesModule} from "../../../angular-modules"; |
||||||
|
|
||||||
|
function wpSingleViewFieldService($filter, |
||||||
|
I18n, |
||||||
|
WorkPackagesHelper,
|
||||||
|
inplaceEditErrors) { |
||||||
|
|
||||||
|
function getSchema(workPackage) { |
||||||
|
return workPackage.schema; |
||||||
|
} |
||||||
|
|
||||||
|
function isEditable(workPackage, field) { |
||||||
|
// no form - no editing
|
||||||
|
if (!workPackage.form) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
var schema = getSchema(workPackage); |
||||||
|
// TODO: extract to strategy if new cases arise
|
||||||
|
if (field === 'date') { |
||||||
|
// nope
|
||||||
|
return schema['startDate'].writable && schema['dueDate'].writable; |
||||||
|
//return workPackage.schema.startDate.writable
|
||||||
|
// && workPackage.schema.dueDate.writable;
|
||||||
|
} |
||||||
|
if(schema[field].type === 'Date') { |
||||||
|
return true; |
||||||
|
} |
||||||
|
var isWritable = schema[field].writable; |
||||||
|
|
||||||
|
// not writable if no embedded allowed values
|
||||||
|
if (isWritable && schema[field]._links && allowedValuesEmbedded(workPackage, field)) { |
||||||
|
isWritable = getEmbeddedAllowedValues(workPackage, field).length > 0; |
||||||
|
} |
||||||
|
|
||||||
|
return isWritable; |
||||||
|
} |
||||||
|
|
||||||
|
function isSpecified(workPackage, field) { |
||||||
|
var schema = getSchema(workPackage); |
||||||
|
if (field === 'date') { |
||||||
|
// kind of specified
|
||||||
|
return true; |
||||||
|
} |
||||||
|
return !_.isUndefined(schema[field]); |
||||||
|
} |
||||||
|
|
||||||
|
// under special conditions fields will be shown
|
||||||
|
// irregardless if they are empty or not
|
||||||
|
// e.g. when an error should trigger the editing state
|
||||||
|
// of an empty field after type change
|
||||||
|
function isHideable(workPackage, field) { |
||||||
|
if (inplaceEditErrors.errors && inplaceEditErrors.errors[field]) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
var attrVisibility = getVisibility(workPackage, field); |
||||||
|
|
||||||
|
var notRequired = !isRequired(workPackage, field); |
||||||
|
var empty = isEmpty(workPackage, field); |
||||||
|
var visible = attrVisibility == 'visible'; // always show
|
||||||
|
var hidden = attrVisibility == 'hidden'; // never show
|
||||||
|
// !hidden && !visible => show if not empty
|
||||||
|
|
||||||
|
if (workPackage.isNew === true) { |
||||||
|
return notRequired && hidden; |
||||||
|
} else { |
||||||
|
return notRequired && !visible && (empty || hidden); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function getVisibility(workPackage, field) { |
||||||
|
if (field == "date") { |
||||||
|
return getDateVisibility(workPackage); |
||||||
|
} else { |
||||||
|
var schema = workPackage.schema; |
||||||
|
var prop = schema && schema[field]; |
||||||
|
|
||||||
|
return prop && prop.visibility; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* There isn't actually a 'date' field for work packages. |
||||||
|
* There are two fields: 'start_date' and 'due_date' |
||||||
|
* Though they are displayed together in one row, as one 'field'. |
||||||
|
* Since the schema doesn't know any field named 'date' we |
||||||
|
* derive the visibility for the imaginary 'date' field from |
||||||
|
* the actual schema values of 'due_date' and 'start_date'. |
||||||
|
* |
||||||
|
* 'visible' > 'default' > 'hidden' |
||||||
|
* Meaning, for instance, that if at least one field is 'visible' |
||||||
|
* both will be shown. Even if the other is 'hidden'. |
||||||
|
* |
||||||
|
* Note: this is duplicated in app/views/types/_form.html.erb |
||||||
|
*/ |
||||||
|
function getDateVisibility(workPackage) { |
||||||
|
var a = getVisibility(workPackage, "startDate"); |
||||||
|
var b = getVisibility(workPackage, "dueDate"); |
||||||
|
var values = [a, b]; |
||||||
|
|
||||||
|
if (_.contains(values, "visible")) { |
||||||
|
return "visible"; |
||||||
|
} else if (_.contains(values, "default")) { |
||||||
|
return "default"; |
||||||
|
} else if (_.contains(values, "hidden")) { |
||||||
|
return "hidden"; |
||||||
|
} else { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function isMilestone(workPackage) { |
||||||
|
// TODO: this should be written as "only use the form when editing"
|
||||||
|
// otherwise always use the simple way
|
||||||
|
// currently we don't know the context in which this method is called
|
||||||
|
var formAvailable = !_.isUndefined(workPackage.form); |
||||||
|
if (formAvailable) { |
||||||
|
var allowedValues = workPackage.schema.type.$embedded.allowedValues; |
||||||
|
var currentType = workPackage.$links.type.$link.href; |
||||||
|
|
||||||
|
return _.some(allowedValues, function(allowedValue) { |
||||||
|
return allowedValue.href === currentType && |
||||||
|
allowedValue.isMilestone; |
||||||
|
}); |
||||||
|
} else { |
||||||
|
return workPackage.type.isMilestone; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function getValue(workPackage, field) { |
||||||
|
var payload = workPackage; |
||||||
|
|
||||||
|
if (field === 'date') { |
||||||
|
if(isMilestone(workPackage)) { |
||||||
|
return payload['dueDate']; |
||||||
|
} |
||||||
|
return { |
||||||
|
startDate: payload['startDate'], |
||||||
|
dueDate: payload['dueDate'] |
||||||
|
}; |
||||||
|
} |
||||||
|
if (!_.isUndefined(payload[field])) { |
||||||
|
return payload[field]; |
||||||
|
} |
||||||
|
if (isEmbedded(payload, field)) { |
||||||
|
return payload.$embedded[field]; |
||||||
|
} |
||||||
|
|
||||||
|
if (payload.$links[field] && payload.$links[field].$link.href !== null) { |
||||||
|
return payload.$links[field]; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
function allowedValuesEmbedded(workPackage, field) { |
||||||
|
var schema = getSchema(workPackage); |
||||||
|
return _.isArray(schema[field]._links.allowedValues); |
||||||
|
} |
||||||
|
|
||||||
|
function getEmbeddedAllowedValues(workPackage, field) { |
||||||
|
var options = []; |
||||||
|
var schema = getSchema(workPackage); |
||||||
|
return schema[field].$embedded.allowedValues; |
||||||
|
} |
||||||
|
|
||||||
|
function isRequired(workPackage, field) { |
||||||
|
var schema = getSchema(workPackage); |
||||||
|
if (_.isUndefined(schema[field])) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return schema[field].required; |
||||||
|
} |
||||||
|
|
||||||
|
function isEmbedded(workPackage, field) { |
||||||
|
return !_.isUndefined(workPackage.$embedded[field]); |
||||||
|
} |
||||||
|
|
||||||
|
function getLabel(workPackage, field) { |
||||||
|
var schema = getSchema(workPackage); |
||||||
|
if (field === 'date') { |
||||||
|
// special case
|
||||||
|
return I18n.t('js.work_packages.properties.date'); |
||||||
|
} |
||||||
|
return schema[field].name; |
||||||
|
} |
||||||
|
|
||||||
|
function isEmpty(workPackage, field) { |
||||||
|
if (field === 'date') { |
||||||
|
return ( |
||||||
|
getValue(workPackage, 'startDate') === null && |
||||||
|
getValue(workPackage, 'dueDate') === null |
||||||
|
); |
||||||
|
} |
||||||
|
var value = format(workPackage, field); |
||||||
|
if (value === null || value === '') { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
if (value.html === '') { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
if (field === 'spentTime' && workPackage[field] === 'PT0S') { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
if (value.$embedded && _.isArray(value.$embedded.elements)) { |
||||||
|
return value.$embedded.elements.length === 0; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
function format(workPackage, field) { |
||||||
|
var schema = getSchema(workPackage); |
||||||
|
if (field === 'date') { |
||||||
|
if(isMilestone(workPackage)) { |
||||||
|
return workPackage['dueDate']; |
||||||
|
} |
||||||
|
return { |
||||||
|
startDate: workPackage.startDate, |
||||||
|
dueDate: workPackage.dueDate, |
||||||
|
noStartDate: I18n.t('js.label_no_start_date'), |
||||||
|
noEndDate: I18n.t('js.label_no_due_date') |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
var value = workPackage[field]; |
||||||
|
if (_.isUndefined(value)) { |
||||||
|
return getValue(workPackage, field, true); |
||||||
|
} |
||||||
|
|
||||||
|
if (value === null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
var fieldMapping = { |
||||||
|
dueDate: 'date', |
||||||
|
startDate: 'date', |
||||||
|
createdAt: 'datetime', |
||||||
|
updatedAt: 'datetime' |
||||||
|
}[field] || schema[field] && schema[field].type; |
||||||
|
|
||||||
|
switch(fieldMapping) { |
||||||
|
case('Duration'): |
||||||
|
var hours = moment.duration(value).asHours(); |
||||||
|
var formattedHours = $filter('number')(hours, 2); |
||||||
|
return I18n.t('js.units.hour', { count: formattedHours }); |
||||||
|
case('Boolean'): |
||||||
|
return value ? I18n.t('js.general_text_yes') : I18n.t('js.general_text_no'); |
||||||
|
case('Date'): |
||||||
|
return value; |
||||||
|
case('Float'): |
||||||
|
return $filter('number')(value); |
||||||
|
default: |
||||||
|
return WorkPackagesHelper.formatValue(value, fieldMapping); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
isEditable: isEditable, |
||||||
|
isRequired: isRequired, |
||||||
|
isSpecified: isSpecified, |
||||||
|
isHideable: isHideable, |
||||||
|
isMilestone: isMilestone, |
||||||
|
isEmbedded: isEmbedded, |
||||||
|
getLabel: getLabel, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
opWorkPackagesModule.service('wpSingleViewField', wpSingleViewFieldService); |
@ -0,0 +1,63 @@ |
|||||||
|
<div ng-if="$ctrl.workPackage" wp-edit-form="$ctrl.workPackage"> |
||||||
|
<div class="attributes-group"> |
||||||
|
<div class="attributes-group--header"> |
||||||
|
<div class="attributes-group--header-container"> |
||||||
|
<h3 class="attributes-group--header-text"> |
||||||
|
{{ $ctrl.workPackage.type.name }} #{{ $ctrl.workPackage.id }} |
||||||
|
</h3> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="attributes-group--header-container-right"> |
||||||
|
<span ng-bind="$ctrl.I18n.t('js.label_last_updated_on')"/> |
||||||
|
<op-date date-value="$ctrl.workPackage.updatedAt"></op-date> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div |
||||||
|
wp-edit-field="'description'" |
||||||
|
class="single-attribute wiki"> |
||||||
|
<wp-display-attr |
||||||
|
schema="$ctrl.workPackage.schema" |
||||||
|
work-package="$ctrl.workPackage" |
||||||
|
attribute="'description'"> |
||||||
|
</wp-display-attr> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div ng-repeat="group in $ctrl.groupedFields" ng-hide="$ctrl.shouldHideGroup(group.groupName)" class="attributes-group"> |
||||||
|
|
||||||
|
<div class="attributes-group--header"> |
||||||
|
<div class="attributes-group--header-container"> |
||||||
|
<h3 class="attributes-group--header-text" |
||||||
|
ng-bind="$ctrl.I18n.t('js.work_packages.property_groups.' + group.groupName)"></h3> |
||||||
|
</div> |
||||||
|
<div class="attributes-group--header-toggle"> |
||||||
|
<panel-expander tabindex="-1" ng-if="$ctrl.wpSingleView.showToggleButton() && $first" |
||||||
|
collapsed="$ctrl.hideEmptyFields" |
||||||
|
expand-text="{{ $ctrl.I18n.t('js.label_show_attributes') }}" |
||||||
|
collapse-text="{{ $ctrl.I18n.t('js.label_hide_attributes') }}"> |
||||||
|
</panel-expander> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="attributes-key-value"> |
||||||
|
<div |
||||||
|
ng-hide="$ctrl.hideEmptyFields && $ctrl.wpSingleView.isFieldHideable($ctrl.workPackage, field)" |
||||||
|
ng-if="$ctrl.wpSingleView.isSpecified($ctrl.workPackage, field)" |
||||||
|
ng-repeat-start="field in group.attributes" class="attributes-key-value--key"> |
||||||
|
{{$ctrl.wpSingleView.getLabel($ctrl.workPackage, field)}} |
||||||
|
<span class="required" ng-if="$ctrl.wpSingleView.hasNiceStar($ctrl.workPackage, field)"> *</span> |
||||||
|
</div> |
||||||
|
<div |
||||||
|
ng-hide="$ctrl.hideEmptyFields && $ctrl.wpSingleView.isFieldHideable($ctrl.workPackage, field)" |
||||||
|
ng-if="$ctrl.wpSingleView.isSpecified($ctrl.workPackage, field)" |
||||||
|
ng-repeat-end |
||||||
|
wp-edit-field="field" |
||||||
|
class="attributes-key-value--value-container"> |
||||||
|
<wp-display-attr schema="$ctrl.workPackage.schema" work-package="$ctrl.workPackage" attribute="field"></wp-display-attr> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<work-package-attachments edit work-package="$ctrl.workPackage" data-ng-show="!$ctrl.hideEmptyFields || $ctrl.filesExist"></work-package-attachments> |
||||||
|
</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.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {opWorkPackagesModule} from "../../../angular-modules"; |
||||||
|
import {scopedObservable} from "../../../helpers/angular-rx-utils"; |
||||||
|
|
||||||
|
export class WorkPackageSingleViewController { |
||||||
|
public workPackage; |
||||||
|
public groupedFields:any[] = []; |
||||||
|
public hideEmptyFields:boolean = true; |
||||||
|
public filesExist:boolean = false; |
||||||
|
|
||||||
|
constructor(protected $scope, |
||||||
|
protected $state, |
||||||
|
protected loadingIndicator, |
||||||
|
protected $stateParams, |
||||||
|
public wpSingleView, |
||||||
|
protected I18n, |
||||||
|
protected wpCacheService, |
||||||
|
protected NotificationsService, |
||||||
|
protected WorkPackagesOverviewService, |
||||||
|
protected WorkPackageFieldService, |
||||||
|
protected inplaceEditAll, |
||||||
|
protected WorkPackageAttachmentsService) { |
||||||
|
|
||||||
|
scopedObservable($scope, wpCacheService.loadWorkPackage($stateParams.workPackageId)).subscribe(wp => { |
||||||
|
this.workPackage = wp; |
||||||
|
this.workPackage.schema.$load(); |
||||||
|
|
||||||
|
this.groupedFields = WorkPackagesOverviewService.getGroupedWorkPackageOverviewAttributes(); |
||||||
|
|
||||||
|
WorkPackageAttachmentsService.hasAttachments(this.workPackage).then(bool => { |
||||||
|
this.filesExist = bool; |
||||||
|
}); |
||||||
|
|
||||||
|
$scope.$watch('$ctrl.workPackage.schema', schema => { |
||||||
|
if (schema) { |
||||||
|
this.wpSingleView.setFocus(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
$scope.$watchCollection('$ctrl.workPackage.form', () => { |
||||||
|
var schema = WorkPackageFieldService.getSchema(this.workPackage); |
||||||
|
var otherGroup:any = _.find(this.groupedFields, {groupName: 'other'}); |
||||||
|
otherGroup.attributes = []; |
||||||
|
|
||||||
|
angular.forEach(schema.props, (prop, propName) => { |
||||||
|
if (propName.match(/^customField/)) { |
||||||
|
otherGroup.attributes.push(propName); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
otherGroup.attributes.sort((a, b) => { |
||||||
|
var getLabel = field => this.wpSingleView.getLabel(this.workPackage, field); |
||||||
|
var left = getLabel(a).toLowerCase(); |
||||||
|
var right = getLabel(b).toLowerCase(); |
||||||
|
|
||||||
|
return left.localeCompare(right); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
$scope.$on('workPackageUpdatedInEditor', () => { |
||||||
|
NotificationsService.addSuccess({ |
||||||
|
message: I18n.t('js.notice_successful_update'), |
||||||
|
link: { |
||||||
|
target: () => { |
||||||
|
loadingIndicator.mainPage = $state.go( |
||||||
|
...["work-packages.show.activity", $state.params]); |
||||||
|
}, |
||||||
|
text: I18n.t('js.work_packages.message_successful_show_in_fullscreen') |
||||||
|
} |
||||||
|
}); |
||||||
|
});
|
||||||
|
} |
||||||
|
|
||||||
|
public shouldHideGroup(group) { |
||||||
|
return this.wpSingleView.shouldHideGroup( |
||||||
|
this.hideEmptyFields, this.groupedFields, group, this.workPackage); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function wpSingleViewDirective() { |
||||||
|
return { |
||||||
|
restrict: 'E', |
||||||
|
templateUrl: '/components/work-packages/wp-single-view/wp-single-view.directive.html', |
||||||
|
|
||||||
|
scope: {}, |
||||||
|
|
||||||
|
bindToController: true, |
||||||
|
controller: WorkPackageSingleViewController, |
||||||
|
controllerAs: '$ctrl' |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
opWorkPackagesModule.directive('wpSingleView', wpSingleViewDirective); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,140 @@ |
|||||||
|
//-- 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.
|
||||||
|
//++
|
||||||
|
|
||||||
|
import {opWorkPackagesModule} from "../../../angular-modules"; |
||||||
|
|
||||||
|
function wpSingleViewService($window, $timeout, wpSingleViewField) { |
||||||
|
// specifies unhideable (during creation)
|
||||||
|
var unhideableFields = [ |
||||||
|
'subject', |
||||||
|
'type', |
||||||
|
'status', |
||||||
|
'description', |
||||||
|
'priority' |
||||||
|
]; |
||||||
|
var firstTimeFocused = false; |
||||||
|
var isGroupHideable = function (groupedFields, groupName, workPackage, cb) { |
||||||
|
if (!workPackage) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
var group = _.find(groupedFields, {groupName: groupName}); |
||||||
|
var isHideable = typeof cb === 'undefined' ? isFieldHideable : cb; |
||||||
|
return group.attributes.length === 0 || _.every(group.attributes, function(field) { |
||||||
|
return isHideable(workPackage, field); |
||||||
|
}); |
||||||
|
}, |
||||||
|
isGroupEmpty = function (groupedFields, groupName) { |
||||||
|
var group = _.find(groupedFields, {groupName: groupName}); |
||||||
|
|
||||||
|
return group.attributes.length === 0; |
||||||
|
}, |
||||||
|
shouldHideGroup = function(hideEmptyActive, groupedFields, groupName, workPackage, cb) { |
||||||
|
return hideEmptyActive && isGroupHideable(groupedFields, groupName, workPackage, cb) || |
||||||
|
!hideEmptyActive && isGroupEmpty(groupedFields, groupName); |
||||||
|
}, |
||||||
|
isFieldHideable = function (workPackage, field) { |
||||||
|
if (!workPackage) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return wpSingleViewField.isHideable(workPackage, field); |
||||||
|
}, |
||||||
|
isFieldHideableOnCreate = function(workPackage, field) { |
||||||
|
if (!workPackage) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (!isSpecified(workPackage, field)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
if (!isEditable(workPackage, field)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
if (_.contains(unhideableFields, field)) { |
||||||
|
return !wpSingleViewField.isEditable(workPackage, field); |
||||||
|
} |
||||||
|
|
||||||
|
if (wpSingleViewField.isRequired(workPackage, field)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return wpSingleViewField.isHideable(workPackage, field); |
||||||
|
}, |
||||||
|
isSpecified = function (workPackage, field) { |
||||||
|
if (!workPackage) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return wpSingleViewField.isSpecified(workPackage, field); |
||||||
|
}, |
||||||
|
isEditable = function(workPackage, field) { |
||||||
|
return wpSingleViewField.isEditable(workPackage, field); |
||||||
|
}, |
||||||
|
hasNiceStar = function (workPackage, field) { |
||||||
|
if (!workPackage) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return wpSingleViewField.isRequired(workPackage, field) && |
||||||
|
wpSingleViewField.isEditable(workPackage, field); |
||||||
|
}, |
||||||
|
getLabel = function (workPackage, field) { |
||||||
|
if (!(workPackage && typeof field === 'string')) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
return wpSingleViewField.getLabel(workPackage, field); |
||||||
|
}, |
||||||
|
setFocus = function() { |
||||||
|
if (!firstTimeFocused) { |
||||||
|
firstTimeFocused = true; |
||||||
|
$timeout(function() { |
||||||
|
// TODO: figure out a better way to fix the wp table columns bug
|
||||||
|
// where arrows are misplaced when not resizing the window
|
||||||
|
angular.element($window).trigger('resize'); |
||||||
|
angular.element('.work-packages--details--subject .focus-input').focus(); |
||||||
|
}); |
||||||
|
} |
||||||
|
}, |
||||||
|
showToggleButton = function () { |
||||||
|
return true; |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
isGroupHideable: isGroupHideable, |
||||||
|
isGroupEmpty: isGroupEmpty, |
||||||
|
shouldHideGroup: shouldHideGroup, |
||||||
|
isFieldHideable: isFieldHideable, |
||||||
|
isFieldHideableOnCreate: isFieldHideableOnCreate, |
||||||
|
isSpecified: isSpecified, |
||||||
|
isEditable: isEditable, |
||||||
|
hasNiceStar: hasNiceStar, |
||||||
|
getLabel: getLabel, |
||||||
|
setFocus: setFocus, |
||||||
|
showToggleButton: showToggleButton |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
opWorkPackagesModule.factory('wpSingleView', wpSingleViewService); |
@ -0,0 +1,11 @@ |
|||||||
|
<wp-single-view></wp-single-view> |
||||||
|
|
||||||
|
<div class="attributes-group"> |
||||||
|
<div class="attributes-group--header"> |
||||||
|
<div class="attributes-group--header-container"> |
||||||
|
<h3 class="attributes-group--header-text">{{ vm.I18n.t('js.label_latest_activity') }}</h3> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<activity-panel template="overview" work-package="workPackage"></activity-panel> |
||||||
|
</div> |
@ -1,17 +0,0 @@ |
|||||||
|
|
||||||
<div wp-edit-form="$ctrl.workPackage"> |
|
||||||
|
|
||||||
<div ng-repeat="column in columns" |
|
||||||
class="{{column.name}}" |
|
||||||
lang="{{column.custom_field && column.custom_field.name_locale || locale}}" |
|
||||||
wp-edit-field="column.name"> |
|
||||||
|
|
||||||
<wp-display-attr attribute="column.name" |
|
||||||
schema="$ctrl.workPackage.schema" |
|
||||||
object="$ctrl.workPackage"> |
|
||||||
</wp-display-attr> |
|
||||||
|
|
||||||
</div> |
|
||||||
|
|
||||||
</div> |
|
||||||
|
|
@ -1,130 +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.
|
|
||||||
//++
|
|
||||||
|
|
||||||
module.exports = function(I18n, $rootScope) { |
|
||||||
'use strict'; |
|
||||||
// private
|
|
||||||
var createNotification = function(message) { |
|
||||||
if(typeof message === 'string') { |
|
||||||
return { message: message }; |
|
||||||
} |
|
||||||
return message; |
|
||||||
}, |
|
||||||
createSuccessNotification = function(message) { |
|
||||||
return _.extend(createNotification(message), { type: 'success' }); |
|
||||||
}, |
|
||||||
createWarningNotification = function(message) { |
|
||||||
return _.extend(createNotification(message), { type: 'warning' }); |
|
||||||
}, |
|
||||||
createErrorNotification = function(message, errors) { |
|
||||||
return _.extend(createNotification(message), { |
|
||||||
type: 'error', |
|
||||||
errors: errors || [] |
|
||||||
}); |
|
||||||
}, |
|
||||||
createNoticeNotification = function(message) { |
|
||||||
return _.extend(createNotification(message), { type: '' }); |
|
||||||
}, |
|
||||||
createWorkPackageUploadNotification = function(message, uploads) { |
|
||||||
if(!uploads) { |
|
||||||
throw new Error('Cannot create an upload notification without uploads!'); |
|
||||||
} |
|
||||||
return _.extend(createNotification(message), { |
|
||||||
type: 'upload', |
|
||||||
uploads: uploads |
|
||||||
}); |
|
||||||
}, |
|
||||||
broadcast = function(event, data) { |
|
||||||
$rootScope.$broadcast(event, data); |
|
||||||
}, |
|
||||||
currentNotifications = [], |
|
||||||
notificationAdded = function(newNotification) { |
|
||||||
var toRemove = currentNotifications.slice(0); |
|
||||||
_.each(toRemove, function(existingNotification) { |
|
||||||
if (newNotification.type === 'success' || newNotification.type === 'error') { |
|
||||||
remove(existingNotification); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
currentNotifications.push(newNotification); |
|
||||||
}, |
|
||||||
notificationRemoved = function(removedNotification) { |
|
||||||
_.remove(currentNotifications, function(element) { |
|
||||||
return element === removedNotification; |
|
||||||
}); |
|
||||||
}, |
|
||||||
clearNotifications = function() { |
|
||||||
_.remove(currentNotifications); |
|
||||||
}; |
|
||||||
|
|
||||||
$rootScope.$on('notification.remove', function(_e, notification) { |
|
||||||
notificationRemoved(notification); |
|
||||||
}); |
|
||||||
|
|
||||||
$rootScope.$on('notifications.clearAll', function() { |
|
||||||
clearNotifications(); |
|
||||||
}); |
|
||||||
|
|
||||||
|
|
||||||
// public
|
|
||||||
var add = function(message) { |
|
||||||
var notification = createNotification(message); |
|
||||||
broadcast('notification.add', notification); |
|
||||||
notificationAdded(notification); |
|
||||||
return notification; |
|
||||||
}, |
|
||||||
addError = function(message, errors) { |
|
||||||
return add(createErrorNotification(message, errors)); |
|
||||||
}, |
|
||||||
addWarning = function(message) { |
|
||||||
return add(createWarningNotification(message)); |
|
||||||
}, |
|
||||||
addSuccess = function(message) { |
|
||||||
return add(createSuccessNotification(message)); |
|
||||||
}, |
|
||||||
addNotice = function(message) { |
|
||||||
return add(createNoticeNotification(message)); |
|
||||||
}, |
|
||||||
addWorkPackageUpload = function(message, uploads) { |
|
||||||
return add(createWorkPackageUploadNotification(message, uploads)); |
|
||||||
}, |
|
||||||
remove = function(notification) { |
|
||||||
broadcast('notification.remove', notification); |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
return { |
|
||||||
add: add, |
|
||||||
remove: remove, |
|
||||||
addError: addError, |
|
||||||
addWarning: addWarning, |
|
||||||
addSuccess: addSuccess, |
|
||||||
addNotice: addNotice, |
|
||||||
addWorkPackageUpload: addWorkPackageUpload |
|
||||||
}; |
|
||||||
}; |
|
@ -1,73 +0,0 @@ |
|||||||
|
|
||||||
<hr> |
|
||||||
Start |
|
||||||
<hr> |
|
||||||
<wp-overview-panel></wp-overview-panel> |
|
||||||
<hr> |
|
||||||
Ende |
|
||||||
<hr> |
|
||||||
|
|
||||||
<div class="attributes-group"> |
|
||||||
|
|
||||||
<div class="attributes-group--header"> |
|
||||||
<div class="attributes-group--header-container"> |
|
||||||
<h3 class="attributes-group--header-text"> |
|
||||||
{{ type.props.name }} #{{ workPackage.props.id }} |
|
||||||
</h3> |
|
||||||
</div> |
|
||||||
<div class="attributes-group--header-container-right"> |
|
||||||
<span ng-bind="I18n.t('js.label_last_updated_on')"/> |
|
||||||
<op-date date-value="workPackage.props.updatedAt"></op-date> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="single-attribute wiki"> |
|
||||||
<wp-field field-name="'description'"></wp-field> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div ng-repeat="group in vm.groupedFields" ng-hide="vm.shouldHideGroup(group.groupName)" class="attributes-group"> |
|
||||||
|
|
||||||
<div class="attributes-group--header"> |
|
||||||
<div class="attributes-group--header-container"> |
|
||||||
<h3 class="attributes-group--header-text" |
|
||||||
ng-bind="I18n.t('js.work_packages.property_groups.' + group.groupName)"></h3> |
|
||||||
</div> |
|
||||||
<div class="attributes-group--header-toggle"> |
|
||||||
<panel-expander tabindex="-1" ng-if="vm.showToggleButton() && $first" |
|
||||||
collapsed="vm.hideEmptyFields" |
|
||||||
expand-text="{{ I18n.t('js.label_show_attributes') }}" |
|
||||||
collapse-text="{{ I18n.t('js.label_hide_attributes') }}"> |
|
||||||
</panel-expander> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="attributes-key-value"> |
|
||||||
<div |
|
||||||
ng-hide="vm.hideEmptyFields && vm.isFieldHideable(vm.workPackage, field)" |
|
||||||
ng-if="vm.isSpecified(vm.workPackage, field)" |
|
||||||
ng-repeat-start="field in group.attributes" class="attributes-key-value--key"> |
|
||||||
{{vm.getLabel(vm.workPackage, field)}} |
|
||||||
<span class="required" ng-if="vm.hasNiceStar(vm.workPackage, field)"> *</span> |
|
||||||
</div> |
|
||||||
<div |
|
||||||
ng-hide="vm.hideEmptyFields && vm.isFieldHideable(vm.workPackage, field)" |
|
||||||
ng-if="vm.isSpecified(vm.workPackage, field)" |
|
||||||
ng-repeat-end |
|
||||||
class="attributes-key-value--value-container"> |
|
||||||
<wp-field field-name="field"></wp-field> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<work-package-attachments edit work-package="vm.workPackage" data-ng-show="!vm.hideEmptyFields || vm.filesExist"></work-package-attachments> |
|
||||||
|
|
||||||
<div class="attributes-group"> |
|
||||||
<div class="attributes-group--header"> |
|
||||||
<div class="attributes-group--header-container"> |
|
||||||
<h3 class="attributes-group--header-text">{{ I18n.t('js.label_latest_activity') }}</h3> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<activity-panel template="overview" work-package="workPackage"></activity-panel> |
|
||||||
</div> |
|
@ -1,104 +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.
|
|
||||||
//++
|
|
||||||
|
|
||||||
module.exports = function( |
|
||||||
$scope, |
|
||||||
WorkPackagesOverviewService, |
|
||||||
WorkPackageFieldService, |
|
||||||
EditableFieldsState, |
|
||||||
inplaceEditAll, |
|
||||||
WorkPackageDisplayHelper, |
|
||||||
NotificationsService, |
|
||||||
WorkPackageAttachmentsService |
|
||||||
) { |
|
||||||
var vm = this; |
|
||||||
|
|
||||||
vm.groupedFields = []; |
|
||||||
vm.hideEmptyFields = true; |
|
||||||
vm.workPackage = $scope.workPackage; |
|
||||||
|
|
||||||
|
|
||||||
//Show all attributes in Edit-Mode
|
|
||||||
$scope.$watch(function(){ |
|
||||||
return inplaceEditAll.state; |
|
||||||
},function(newState, oldState){ |
|
||||||
if(newState !== oldState){ |
|
||||||
vm.hideEmptyFields = !newState; |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
vm.shouldHideGroup = function(group) { |
|
||||||
return WorkPackageDisplayHelper.shouldHideGroup(vm.hideEmptyFields, |
|
||||||
vm.groupedFields, |
|
||||||
group, |
|
||||||
vm.workPackage); |
|
||||||
}; |
|
||||||
vm.isFieldHideable = WorkPackageDisplayHelper.isFieldHideable; |
|
||||||
vm.getLabel = WorkPackageDisplayHelper.getLabel; |
|
||||||
vm.isSpecified = WorkPackageDisplayHelper.isSpecified; |
|
||||||
vm.hasNiceStar = WorkPackageDisplayHelper.hasNiceStar; |
|
||||||
vm.showToggleButton = WorkPackageDisplayHelper.showToggleButton; |
|
||||||
vm.filesExist = false; |
|
||||||
activate(); |
|
||||||
|
|
||||||
WorkPackageAttachmentsService.hasAttachments(vm.workPackage).then(function(bool) { |
|
||||||
vm.filesExist = bool; |
|
||||||
}); |
|
||||||
|
|
||||||
function activate() { |
|
||||||
$scope.$watch('workPackage.schema', function(schema) { |
|
||||||
if (schema) { |
|
||||||
WorkPackageDisplayHelper.setFocus(); |
|
||||||
vm.workPackage = $scope.workPackage; |
|
||||||
} |
|
||||||
}); |
|
||||||
vm.groupedFields = WorkPackagesOverviewService.getGroupedWorkPackageOverviewAttributes(); |
|
||||||
|
|
||||||
$scope.$watchCollection('vm.workPackage.form', function() { |
|
||||||
var schema = WorkPackageFieldService.getSchema(vm.workPackage); |
|
||||||
var otherGroup = _.find(vm.groupedFields, {groupName: 'other'}); |
|
||||||
otherGroup.attributes = []; |
|
||||||
_.forEach(schema.props, function(prop, propName) { |
|
||||||
if (propName.match(/^customField/)) { |
|
||||||
otherGroup.attributes.push(propName); |
|
||||||
} |
|
||||||
}); |
|
||||||
otherGroup.attributes.sort(function(a, b) { |
|
||||||
var getLabel = function(field) { |
|
||||||
return vm.getLabel(vm.workPackage, field); |
|
||||||
}; |
|
||||||
var left = getLabel(a).toLowerCase(), |
|
||||||
right = getLabel(b).toLowerCase(); |
|
||||||
return left.localeCompare(right); |
|
||||||
}); |
|
||||||
}); |
|
||||||
$scope.$on('workPackageUpdatedInEditor', function() { |
|
||||||
NotificationsService.addSuccess(I18n.t('js.notice_successful_update')); |
|
||||||
}); |
|
||||||
} |
|
||||||
}; |
|
@ -1,105 +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.
|
|
||||||
//++
|
|
||||||
|
|
||||||
module.exports = function(Upload, PathHelper, I18n, NotificationsService, $q, $timeout, $http) { |
|
||||||
'use strict'; |
|
||||||
var upload = function(workPackage, files) { |
|
||||||
var uploadPath = workPackage.links.addAttachment.url(); |
|
||||||
// for file in files build some promises, create a notification per WP,
|
|
||||||
// notify the noticiation (wat?) about progress
|
|
||||||
var uploads = _.map(files, function(file) { |
|
||||||
var options = { |
|
||||||
url: uploadPath, |
|
||||||
fields: { |
|
||||||
metadata: { |
|
||||||
fileName: file.name, |
|
||||||
description: file.description |
|
||||||
} |
|
||||||
}, |
|
||||||
file: file |
|
||||||
}; |
|
||||||
return Upload.upload(options); |
|
||||||
}); |
|
||||||
|
|
||||||
// notify the user
|
|
||||||
var message = I18n.t('js.label_upload_notification', { |
|
||||||
id: workPackage.props.id, |
|
||||||
subject: workPackage.props.subject |
|
||||||
}); |
|
||||||
|
|
||||||
var notification = NotificationsService.addWorkPackageUpload(message, uploads); |
|
||||||
var allUploadsDone = $q.defer(); |
|
||||||
$q.all(uploads).then(function() { |
|
||||||
$timeout(function() { // let the notification linger for a bit
|
|
||||||
NotificationsService.remove(notification); |
|
||||||
allUploadsDone.resolve(); |
|
||||||
}, 700); |
|
||||||
}, function(err) { |
|
||||||
allUploadsDone.reject(err); |
|
||||||
}); |
|
||||||
return allUploadsDone.promise; |
|
||||||
}, |
|
||||||
load = function(workPackage, reload) { |
|
||||||
var path = workPackage.links.attachments.url(), |
|
||||||
attachments = $q.defer(); |
|
||||||
$http.get(path, { cache: !reload }).success(function(response) { |
|
||||||
attachments.resolve(response._embedded.elements); |
|
||||||
}).error(function(err) { |
|
||||||
attachments.reject(err); |
|
||||||
}); |
|
||||||
return attachments.promise; |
|
||||||
}, |
|
||||||
remove = function(fileOrAttachment) { |
|
||||||
var removal = $q.defer(); |
|
||||||
if (angular.isObject(fileOrAttachment._links)) { |
|
||||||
var path = fileOrAttachment._links.self.href; |
|
||||||
$http.delete(path).success(function() { |
|
||||||
removal.resolve(fileOrAttachment); |
|
||||||
}).error(function(err) { |
|
||||||
removal.reject(err); |
|
||||||
}); |
|
||||||
} else { |
|
||||||
removal.resolve(fileOrAttachment); |
|
||||||
} |
|
||||||
return removal.promise; |
|
||||||
}, |
|
||||||
hasAttachments = function(workPackage) { |
|
||||||
var existance = $q.defer(); |
|
||||||
load(workPackage).then(function(attachments) { |
|
||||||
existance.resolve(attachments.length > 0); |
|
||||||
}); |
|
||||||
return existance.promise; |
|
||||||
}; |
|
||||||
|
|
||||||
return { |
|
||||||
upload: upload, |
|
||||||
remove: remove, |
|
||||||
load: load, |
|
||||||
hasAttachments: hasAttachments |
|
||||||
}; |
|
||||||
}; |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue