From fffcab7f2bbcde3651a2d3a45021c67059138b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 19 Jul 2017 10:27:24 +0200 Subject: [PATCH] Extend attachment-formattable to other work package fields --- .../single_view/_single_view.sass | 3 +- .../work-package-resource.service.ts | 1 + .../work-package-cache.service.ts | 4 +- .../models/field-model.ts | 34 ------------- .../models/work-package-field-model.ts | 48 +++++++++++++++++++ .../wp-attachments-formattable.directive.ts | 39 +++++---------- .../wp-single-view.directive.html | 12 ++++- .../wp-single-view.directive.ts | 2 +- .../wp-display-formattable-field.module.ts | 2 +- .../wp-display-field.module.ts | 2 +- ...wp-edit-wiki-textarea-field.directive.html | 2 +- .../wp-edit-wiki-textarea-field.module.ts | 4 +- .../wp-edit-field-group.directive.ts | 19 ++++++++ .../wp-edit-field/wp-edit-field.directive.ts | 7 --- .../upload-progress-directive.js | 3 +- 15 files changed, 104 insertions(+), 78 deletions(-) delete mode 100644 frontend/app/components/work-packages/wp-attachments-formattable-field/models/field-model.ts create mode 100644 frontend/app/components/work-packages/wp-attachments-formattable-field/models/work-package-field-model.ts diff --git a/app/assets/stylesheets/content/work_packages/single_view/_single_view.sass b/app/assets/stylesheets/content/work_packages/single_view/_single_view.sass index d4b2693ec2..5129c1ed98 100644 --- a/app/assets/stylesheets/content/work_packages/single_view/_single_view.sass +++ b/app/assets/stylesheets/content/work_packages/single_view/_single_view.sass @@ -63,6 +63,8 @@ .work-package--single-view // Make elements in split and full view span the entire width + // Style the edit field element when the full width is required + .wp-edit-formattable-field, .wp-edit-field width: 100% @@ -160,7 +162,6 @@ i padding: 0 margin: 0 - // Implement two column layout for WP full screen view @media screen and (min-width: 90rem) .action-show .attributes-group, diff --git a/frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.ts b/frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.ts index eafa962449..ccbb6ef900 100644 --- a/frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.ts +++ b/frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.ts @@ -435,6 +435,7 @@ export class WorkPackageResource extends HalResource { delete this.$pristine[key]; }); + wpCacheService.updateWorkPackage(this as any); deferred.resolve(this); }); }) diff --git a/frontend/app/components/work-packages/work-package-cache.service.ts b/frontend/app/components/work-packages/work-package-cache.service.ts index 28d72b59c7..b4b0cc139e 100644 --- a/frontend/app/components/work-packages/work-package-cache.service.ts +++ b/frontend/app/components/work-packages/work-package-cache.service.ts @@ -67,7 +67,7 @@ export class WorkPackageCacheService extends StateCacheService { this.schemaCacheService.ensureLoaded(workPackage).then(() => { - this.updateValue(id, workPackage); + this.multiState.get(id).putValue(workPackage); resolve(workPackage); }, reject); }, reject); diff --git a/frontend/app/components/work-packages/wp-attachments-formattable-field/models/field-model.ts b/frontend/app/components/work-packages/wp-attachments-formattable-field/models/field-model.ts deleted file mode 100644 index 0755a0fc53..0000000000 --- a/frontend/app/components/work-packages/wp-attachments-formattable-field/models/field-model.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {InsertMode} from '../wp-attachments-formattable.enums'; -import {IApplyAttachmentMarkup} from '../wp-attachments-formattable.interfaces'; -import {WorkPackageResourceInterface} from '../../../api/api-v3/hal-resources/work-package-resource.service'; -import {MarkupModel} from './markup-model'; -import IAugmentedJQuery = angular.IAugmentedJQuery; - -export class FieldModel implements IApplyAttachmentMarkup { - public contentToInsert:string; - - constructor(protected workPackage:WorkPackageResourceInterface, protected markupModel:MarkupModel) { - this.contentToInsert = workPackage.description.raw || ''; - } - - private addInitialLineBreak():string { - return (this.contentToInsert.length > 0) ? '\r\n' : ''; - } - - public insertAttachmentLink(url:string, insertMode:InsertMode, addLineBreak?:boolean):void { - this.contentToInsert += this.addInitialLineBreak() + this.markupModel.createMarkup(url, - insertMode, - false); - } - - public insertWebLink(url:string, insertMode:InsertMode):void { - this.contentToInsert += this.addInitialLineBreak() + this.markupModel.createMarkup(url, - insertMode, - false); - } - - public save():void { - this.workPackage.description.raw = this.contentToInsert; - this.workPackage.save(); - } -} diff --git a/frontend/app/components/work-packages/wp-attachments-formattable-field/models/work-package-field-model.ts b/frontend/app/components/work-packages/wp-attachments-formattable-field/models/work-package-field-model.ts new file mode 100644 index 0000000000..e994b04bf0 --- /dev/null +++ b/frontend/app/components/work-packages/wp-attachments-formattable-field/models/work-package-field-model.ts @@ -0,0 +1,48 @@ +import {InsertMode} from '../wp-attachments-formattable.enums'; +import {IApplyAttachmentMarkup} from '../wp-attachments-formattable.interfaces'; +import {WorkPackageResourceInterface} from '../../../api/api-v3/hal-resources/work-package-resource.service'; +import {MarkupModel} from './markup-model'; +import {WorkPackageCacheService} from '../../work-package-cache.service'; +import {$injectFields} from '../../../angular/angular-injector-bridge.functions'; + +export class WorkPackageFieldModel implements IApplyAttachmentMarkup { + public wpCacheService:WorkPackageCacheService; + public contentToInsert:string; + + constructor(protected workPackage:WorkPackageResourceInterface, protected attribute:string, protected markupModel:MarkupModel) { + $injectFields(this, 'wpCacheService'); + + const formattable = workPackage[attribute]; + this.contentToInsert = _.get(formattable, 'raw') as string || ''; + } + + private addInitialLineBreak():string { + return (this.contentToInsert.length > 0) ? '\r\n' : ''; + } + + public insertAttachmentLink(url:string, insertMode:InsertMode, addLineBreak?:boolean):void { + this.contentToInsert += this.addInitialLineBreak() + this.markupModel.createMarkup(url, + insertMode, + false); + } + + public insertWebLink(url:string, insertMode:InsertMode):void { + this.contentToInsert += this.addInitialLineBreak() + this.markupModel.createMarkup(url, + insertMode, + false); + } + + public save():void { + let value = this.workPackage[this.attribute] || { raw: '', html: '' }; + value.raw = this.contentToInsert; + + this.workPackage[this.attribute] = value; + this.workPackage + .save() + .then((wp) => { + // Refresh the work package some time later as there is no way to tell + // whether the attachment was uploaded successfully AND the field was updated. + setTimeout(() => this.wpCacheService.require(wp.id, true), 150); + }); + } +} diff --git a/frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts b/frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts index 1d7615cd67..ea44ed4546 100644 --- a/frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts +++ b/frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts @@ -1,16 +1,15 @@ import {InsertMode, ViewMode} from './wp-attachments-formattable.enums'; import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; -import {WorkPackageSingleViewController} from '../wp-single-view/wp-single-view.directive'; import {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service'; import {openprojectModule} from '../../../angular-modules'; import {WorkPackageCacheService} from '../work-package-cache.service'; import {MarkupModel} from './models/markup-model'; import {EditorModel} from './models/editor-model'; import {PasteModel} from './models/paste-model'; -import {FieldModel} from './models/field-model'; +import {WorkPackageFieldModel} from './models/work-package-field-model'; import {DropModel} from './models/drop-model'; import {SingleAttachmentModel} from './models/single-attachment'; -import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service'; +import {WorkPackageSingleViewController} from '../wp-single-view/wp-single-view.directive'; export class WpAttachmentsFormattableController { constructor(protected $scope:ng.IScope, @@ -18,7 +17,6 @@ export class WpAttachmentsFormattableController { protected $rootScope:ng.IRootScopeService, protected $location:ng.ILocationService, protected wpCacheService:WorkPackageCacheService, - protected wpEditing:WorkPackageEditingService, protected $timeout:ng.ITimeoutService, protected $q:ng.IQService, protected $state:ng.ui.IStateService, @@ -43,7 +41,7 @@ export class WpAttachmentsFormattableController { const [, editor] = this.getEditor(); const originalEvent = (evt.originalEvent as DragEvent); - const workPackage:WorkPackageResourceInterface = (this.$scope as any).workPackage; + const workPackage:WorkPackageResourceInterface = this.$scope.workPackage; const dropData:DropModel = new DropModel(this.$location, originalEvent.dataTransfer, workPackage); @@ -87,7 +85,7 @@ export class WpAttachmentsFormattableController { * Get the editor model for the current view mode. * This is either the editing model (open textarea field), or the closed field model. */ - protected getEditor():[ViewMode, EditorModel | FieldModel] { + protected getEditor():[ViewMode, EditorModel | WorkPackageFieldModel] { const textarea:ng.IAugmentedJQuery = this.$element.find('textarea'); let viewMode; @@ -98,20 +96,19 @@ export class WpAttachmentsFormattableController { model = new EditorModel(textarea, new MarkupModel()); } else { viewMode = ViewMode.SHOW; - model = new FieldModel(this.$scope.workPackage, new MarkupModel()); + model = new WorkPackageFieldModel(this.$scope.workPackage, this.$scope.attribute, new MarkupModel()); } return [viewMode, model]; } - protected uploadAndInsert(files:File[], model:EditorModel | FieldModel) { - const workPackage:WorkPackageResourceInterface = this.$scope.workPackage; - - if (workPackage.isNew) { - return this.insertDelayedAttachments(files, model, workPackage); + protected uploadAndInsert(files:File[], model:EditorModel | WorkPackageFieldModel) { + const wp = this.$scope.workPackage as WorkPackageResourceInterface; + if (wp.isNew) { + return this.insertDelayedAttachments(files, model, wp); } - workPackage + wp .uploadAttachments(files) .then(attachments => attachments.elements) .then((updatedAttachments:any) => { @@ -206,28 +203,18 @@ export class WpAttachmentsFormattableController { }; } -interface IAttachmentScope extends ng.IScope { - workPackage:WorkPackageResourceInterface; -} - function wpAttachmentsFormattable() { return { bindToController: true, controller: WpAttachmentsFormattableController, - link: (scope:IAttachmentScope, + link: (scope:ng.IScope, element:ng.IAugmentedJQuery, attrs:ng.IAttributes, controllers:[WorkPackageSingleViewController]) => { - // right now the attachments directive will only work in combination with either - // the wpSingleView or the wpEditForm directive - // else the drop handler will fail because of a missing reference to the current wp - if (angular.isUndefined(controllers[0])) { - return; - } - scope.workPackage = controllers[0].workPackage; + scope.attribute = scope.$eval(attrs.fieldName); }, - require: ['?^wpSingleView', '?^wpEditForm'], + require: ['^wpSingleView'], restrict: 'A' }; } diff --git a/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html b/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html index cea8e270c6..6fa97f2784 100644 --- a/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html +++ b/frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html @@ -73,7 +73,17 @@
- + + + + +
-
diff --git a/frontend/app/components/wp-edit/field-types/wp-edit-wiki-textarea-field.module.ts b/frontend/app/components/wp-edit/field-types/wp-edit-wiki-textarea-field.module.ts index 8dfe4eb81a..f333e227a9 100644 --- a/frontend/app/components/wp-edit/field-types/wp-edit-wiki-textarea-field.module.ts +++ b/frontend/app/components/wp-edit/field-types/wp-edit-wiki-textarea-field.module.ts @@ -65,8 +65,8 @@ export class WikiTextareaEditField extends EditField { }; } - public get supportsAttachments() { - return this.name === 'description'; + public get isFormattable() { + return true; } public isEmpty(): boolean { diff --git a/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field-group.directive.ts b/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field-group.directive.ts index c9c531230d..6cce314c9a 100644 --- a/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field-group.directive.ts +++ b/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field-group.directive.ts @@ -33,6 +33,8 @@ import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing import {WorkPackageEditForm} from '../../wp-edit-form/work-package-edit-form'; import {SingleViewEditContext} from '../../wp-edit-form/single-view-edit-context'; import {input} from 'reactivestates'; +import {scopeDestroyed$} from '../../../helpers/angular-rx-utils'; +import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; export class WorkPackageEditFieldGroupController { public workPackageId:string; @@ -65,6 +67,13 @@ export class WorkPackageEditFieldGroupController { } public $onInit() { + this.states.workPackages.get(this.workPackageId) + .values$() + .takeUntil(scopeDestroyed$(this.$scope)) + .subscribe((wp) => { + _.each(this.fields, (ctrl) => this.update(ctrl, wp)); + }); + if (this.inEditMode) { this.start(); } @@ -82,6 +91,11 @@ export class WorkPackageEditFieldGroupController { if (form && form.editMode) { field.activateOnForm(form, true); + } else { + this.states.workPackages + .get(this.workPackageId) + .valuesPromise() + .then(wp => this.update(field, wp!)); } } @@ -103,6 +117,11 @@ export class WorkPackageEditFieldGroupController { this.wpEditing.stopEditing(this.workPackageId); } + private update(field:WorkPackageEditFieldController, wp:WorkPackageResourceInterface) { + field.workPackage = wp; + field.render(); + } + private get editContext() { return new SingleViewEditContext(this); } diff --git a/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.directive.ts b/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.directive.ts index e59635468f..cdd21fe009 100644 --- a/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.directive.ts +++ b/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.directive.ts @@ -70,13 +70,6 @@ export class WorkPackageEditFieldController { public $onInit() { this.wpEditFieldGroup.register(this); - this.states.workPackages.get(this.workPackageId) - .values$() - .takeUntil(scopeDestroyed$(this.$scope)) - .subscribe((wp) => { - this.workPackage = wp; - this.render(); - }); } public render() { diff --git a/frontend/app/ui_components/upload-progress-directive.js b/frontend/app/ui_components/upload-progress-directive.js index 5cbc6c33bc..ee5c742411 100644 --- a/frontend/app/ui_components/upload-progress-directive.js +++ b/frontend/app/ui_components/upload-progress-directive.js @@ -31,7 +31,8 @@ module.exports = function() { var uploadProgressController = function(scope) { scope.upload.progress(function(details) { - scope.file = details.config.file.name; + const file = details.config.file || details.config.data.file; + scope.file = _.get(file, 'name', ''); if (details.lengthComputable) { scope.value = Math.round(details.loaded / details.total * 100); } else {