Add final touch to drag and drop handling of attachments (#4632)

-highlight class on dragover to show the ability to drop files on dragover @HDinger feel free to change the styling if you have any better looking ideas

- show workpackage in details pane after drop of an attachment in wp-table

- separated code segments of our former drop handler into several methods for better readability

- bugfix for the "whitespace bug": if the user did not enter a whitespace before and after the attachment markup it got not rendered correctly (eg: this is my text!any-image.png! => this is my text !any-image.png!)
pull/4635/head
Manu Schiller 8 years ago committed by Oliver Günther
parent 7b960efe87
commit d028ac1eac
  1. 12
      app/assets/stylesheets/content/work_package_details/_attachments_tab.sass
  2. 151
      frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts
  3. 8
      frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.models.ts
  4. 11
      frontend/app/components/work-packages/wp-attachments/wp-attachments.directive.ts
  5. 2
      frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html

@ -52,3 +52,15 @@
margin: 20px 0 0 0
padding: 20px 0 0 0
border-top: 1px solid #ddd
.is-droppable
transition: background 0.2s ease, padding 0.2s ease, border 0.2s ease
cursor: copy
border: 1px dotted #35c53f
background: #d8fdd1 !important
form
background: white
textarea
background: #e6fde4
border: 0

@ -10,6 +10,7 @@ import {
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageSingleViewController} from '../wp-single-view/wp-single-view.directive';
import {WorkPackageEditFormController} from '../../wp-edit/wp-edit-form.directive';
import {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service';
import {openprojectModule} from '../../../angular-modules';
import {WorkPackageCacheService} from '../work-package-cache.service';
import {
@ -26,11 +27,16 @@ export class WpAttachmentsFormattableController {
protected $location:ng.ILocationService,
protected wpCacheService:WorkPackageCacheService,
protected wpAttachments:WpAttachmentsService,
protected $timeout:ng.ITimeoutService) {
protected $timeout:ng.ITimeoutService,
protected $q:ng.IQService,
protected $state,
protected loadingIndicator,
protected keepTab:KeepTabService) {
$element.on('drop', this.handleDrop);
$element.on('dragover',this.highlightDroppable);
$element.on('dragleave',this.removeHighlight);
$element.on('dragenter dragleave dragover', this.prevDefault);
}
public handleDrop = (evt:JQueryEventObject):void => {
@ -60,65 +66,111 @@ export class WpAttachmentsFormattableController {
if (dropData.isUpload) {
if (dropData.filesAreValidForUploading()) {
if (!dropData.isDelayedUpload) {
this.wpAttachments.upload(workPackage, dropData.files).then(() => {
workPackage.attachments.$load(true).then((collection:CollectionResourceInterface) => {
const updatedAttachments = collection.elements;
this.wpCacheService.updateWorkPackage(workPackage);
if (angular.isUndefined(updatedAttachments)) {
return;
}
updatedAttachments.sort(function (a, b) {
return a.id > b.id ? 1 : -1;
});
if (dropData.filesCount === 1) {
const currentFile:SingleAttachmentModel =
new SingleAttachmentModel(updatedAttachments[updatedAttachments.length - 1]);
description.insertAttachmentLink(
currentFile.url,
(currentFile.isAnImage) ? InsertMode.INLINE : InsertMode.ATTACHMENT);
}
else if (dropData.filesCount > 1) {
for (let i:number = updatedAttachments.length - 1;
i >= updatedAttachments.length - dropData.filesCount;
i--) {
description.insertAttachmentLink(
updatedAttachments[i].downloadLocation.href,
InsertMode.ATTACHMENT,
true);
}
}
description.save();
}, err => {
console.log('error while reloading attachments', err);
});
}, function (err) {
console.log(err);
this.uploadFiles(workPackage,dropData).then((updatedAttachments:any)=>{
if (angular.isUndefined(updatedAttachments)) {
return;
}
updatedAttachments = this.sortAttachments(updatedAttachments);
if (dropData.filesCount === 1) {
this.insertSingleAttachment(updatedAttachments, description);
}
else if (dropData.filesCount > 1) {
this.insertMultipleAttachments(dropData, updatedAttachments, description);
}
description.save();
});
} else {
dropData.files.forEach((file:File) => {
description.insertAttachmentLink(file.name.replace(/ /g, '_'), InsertMode.ATTACHMENT, true);
file['isPending'] = true;
this.$rootScope.$emit('work_packages.attachment.add', file);
});
description.save();
this.insertDelayedAttachments(dropData,description);
}
}
} else {
const insertUrl:string = dropData.isAttachmentOfCurrentWp() ? dropData.removeHostInformationFromUrl() : dropData.webLinkUrl;
const insertAlternative:InsertMode = dropData.isWebImage() ? InsertMode.INLINE : InsertMode.LINK;
const insertMode:InsertMode = dropData.isAttachmentOfCurrentWp() ? InsertMode.ATTACHMENT : insertAlternative;
description.insertWebLink(insertUrl, insertMode);
description.save();
this.insertUrls(dropData,description);
}
this.openDetailsView(workPackage.id);
this.removeHighlight();
};
protected uploadFiles(workPackage:WorkPackageResourceInterface, dropData:DropModel){
const updatedAttachmentsList = this.$q.defer();
this.wpAttachments.upload(workPackage, dropData.files).then(() => {
workPackage.attachments.$load(true).then((collection:CollectionResourceInterface) => {
updatedAttachmentsList.resolve(collection.elements);
});
});
return updatedAttachmentsList.promise;
}
protected sortAttachments(updatedAttachments:any){
updatedAttachments.sort(function (a:any, b:any) {
return a.id > b.id ? 1 : -1;
});
return updatedAttachments;
}
protected insertSingleAttachment(updatedAttachments:any, description:any){
const currentFile:SingleAttachmentModel =
new SingleAttachmentModel(updatedAttachments[updatedAttachments.length - 1]);
description.insertAttachmentLink(
currentFile.url,
(currentFile.isAnImage) ? InsertMode.INLINE : InsertMode.ATTACHMENT);
}
protected insertMultipleAttachments(dropData:DropModel,updatedAttachments:any,description:any):void{
for (let i:number = updatedAttachments.length - 1;
i >= updatedAttachments.length - dropData.filesCount;
i--) {
description.insertAttachmentLink(
updatedAttachments[i]._links.downloadLocation.href,
InsertMode.ATTACHMENT,
true);
}
}
protected insertDelayedAttachments(dropData:DropModel, description):void{
dropData.files.forEach((file:File) => {
description.insertAttachmentLink(file.name.replace(/ /g, '_'), InsertMode.ATTACHMENT, true);
// implement pending attachments logic when create is ready
});
description.save();
}
protected insertUrls(dropData: DropModel,description):void{
const insertUrl:string = dropData.isAttachmentOfCurrentWp() ? dropData.removeHostInformationFromUrl() : dropData.webLinkUrl;
const insertAlternative:InsertMode = dropData.isWebImage() ? InsertMode.INLINE : InsertMode.LINK;
const insertMode:InsertMode = dropData.isAttachmentOfCurrentWp() ? InsertMode.ATTACHMENT : insertAlternative;
description.insertWebLink(insertUrl, insertMode);
description.save();
}
protected openDetailsView(wpId):void {
if(this.$state.current.name.indexOf('work-packages.list') > -1 && this.$state.params.workPackageId !== wpId){
this.loadingIndicator.mainPage = this.$state.go(this.keepTab.currentDetailsState, {
workPackageId: wpId
});
}
}
protected prevDefault(evt:DragEvent):void {
evt.preventDefault();
evt.stopPropagation();
}
protected highlightDroppable=()=>{
if(!this.$element.hasClass('is-droppable')){
this.$element.addClass('is-droppable');
}
};
protected removeHighlight=()=>{
this.$element.removeClass('is-droppable');
};
}
interface IAttachmentScope extends ng.IScope {
@ -139,7 +191,6 @@ function wpAttachmentsFormattable() {
if (angular.isUndefined(controllers[0] && angular.isUndefined(controllers[1]))) {
return;
}
scope.workPackage = !controllers[0] ? controllers[1].workPackage : controllers[0].workPackage;
},
require: ['?^wpSingleView', '?^wpEditForm'],

@ -42,17 +42,17 @@ export class MarkupModel {
return '';
}
var markup:string = '';
var markup:string = ' ';
switch (insertMode) {
case InsertMode.ATTACHMENT:
markup = 'attachment:' + insertUrl.split('/').pop();
markup += 'attachment:' + insertUrl.split('/').pop();
break;
case InsertMode.DELAYED_ATTACHMENT:
markup = 'attachment:' + insertUrl;
markup += 'attachment:' + insertUrl;
break;
case InsertMode.INLINE:
markup = '!' + insertUrl + '!';
markup += '!' + insertUrl + '!';
break;
case InsertMode.LINK:
markup += insertUrl;

@ -34,7 +34,6 @@ import {WorkPackageCacheService} from '../work-package-cache.service';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {CollectionResourceInterface} from '../../api/api-v3/hal-resources/collection-resource.service';
export class WorkPackageAttachmentsController {
public workPackage:any;
public hideEmptyFields:boolean;
@ -53,6 +52,7 @@ export class WorkPackageAttachmentsController {
public size:any;
private currentlyFocussing;
public wpSingleView:ng.IScope;
constructor(protected $scope:any,
protected $element:ng.IAugmentedJQuery,
@ -112,11 +112,12 @@ export class WorkPackageAttachmentsController {
this.loading = true;
return this.workPackage.attachments.$load(refresh)
.then((collection:CollectionResourceInterface) => {
this.attachments = collection.elements;
return this.attachments;
this.attachments.length = 0;
angular.extend(this.attachments, collection.elements);
})
.finally(() => {
this.loading = false;
this.$scope.wpSingleView.filesExist = this.attachments.length > 0;
});
}
@ -168,11 +169,15 @@ function wpAttachmentsDirective():ng.IDirective {
controller: WorkPackageAttachmentsController,
controllerAs: 'vm',
replace: true,
require: ['?^wpSingleView'],
restrict: 'E',
scope: {
workPackage: '&',
hideEmptyFields: '='
},
link: function(scope,element,attrs,controllers){
(scope as any).wpSingleView = !controllers[0] ? {} : controllers[0];
},
templateUrl: '/components/work-packages/wp-attachments/wp-attachments.directive.html'
};
}

@ -102,6 +102,6 @@
<wp-attachments
work-package="$ctrl.workPackage"
hide-empty-fields="$ctrl.hideEmptyFields">
data-ng-show="!$ctrl.hideEmptyFields || $ctrl.filesExist">
</wp-attachments>
</div>

Loading…
Cancel
Save