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" |
||||
ng-switch="vm.displayType" |
||||
wp-field="vm.workPackage" |
||||
field-name="vm.attribute"> |
||||
ng-switch="$ctrl.displayType" |
||||
wp-field="$ctrl.workPackage" |
||||
field-name="$ctrl.attribute"> |
||||
<progress-bar ng-switch-when="Percent" |
||||
progress="vm.displayText" |
||||
progress="$ctrl.displayText" |
||||
width="80px"> |
||||
</progress-bar> |
||||
|
||||
<span ng-switch-when="SelfLink" title="{{ vm.displayText }}"> |
||||
<a ng-href="{{ vm.displayLink }}">{{ vm.displayText }}</a> |
||||
<span ng-switch-when="SelfLink" title="{{ $ctrl.displayText }}"> |
||||
<a ng-href="{{ $ctrl.displayLink }}">{{ $ctrl.displayText }}</a> |
||||
</span> |
||||
|
||||
<span ng-switch-default |
||||
title="{{ vm.displayText }}" |
||||
ng-class="{ 'work-package--placeholder' : vm.displayText == '-' }"> |
||||
{{ vm.displayText }} |
||||
title="{{ $ctrl.displayText }}" |
||||
ng-class="{ 'work-package--placeholder' : $ctrl.displayText == '-' }"> |
||||
{{ $ctrl.displayText }} |
||||
</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