Extend attachment-formattable to other work package fields

pull/5771/head
Oliver Günther 7 years ago
parent 668306b12f
commit fffcab7f2b
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 3
      app/assets/stylesheets/content/work_packages/single_view/_single_view.sass
  2. 1
      frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.ts
  3. 4
      frontend/app/components/work-packages/work-package-cache.service.ts
  4. 34
      frontend/app/components/work-packages/wp-attachments-formattable-field/models/field-model.ts
  5. 48
      frontend/app/components/work-packages/wp-attachments-formattable-field/models/work-package-field-model.ts
  6. 39
      frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts
  7. 12
      frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.html
  8. 2
      frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.ts
  9. 2
      frontend/app/components/wp-display/field-types/wp-display-formattable-field.module.ts
  10. 2
      frontend/app/components/wp-display/wp-display-field/wp-display-field.module.ts
  11. 2
      frontend/app/components/wp-edit/field-types/wp-edit-wiki-textarea-field.directive.html
  12. 4
      frontend/app/components/wp-edit/field-types/wp-edit-wiki-textarea-field.module.ts
  13. 19
      frontend/app/components/wp-edit/wp-edit-field/wp-edit-field-group.directive.ts
  14. 7
      frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.directive.ts
  15. 3
      frontend/app/ui_components/upload-progress-directive.js

@ -63,6 +63,8 @@
.work-package--single-view .work-package--single-view
// Make elements in split and full view span the entire width // 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 .wp-edit-field
width: 100% width: 100%
@ -160,7 +162,6 @@ i
padding: 0 padding: 0
margin: 0 margin: 0
// Implement two column layout for WP full screen view // Implement two column layout for WP full screen view
@media screen and (min-width: 90rem) @media screen and (min-width: 90rem)
.action-show .attributes-group, .action-show .attributes-group,

@ -435,6 +435,7 @@ export class WorkPackageResource extends HalResource {
delete this.$pristine[key]; delete this.$pristine[key];
}); });
wpCacheService.updateWorkPackage(this as any);
deferred.resolve(this); deferred.resolve(this);
}); });
}) })

@ -67,7 +67,7 @@ export class WorkPackageCacheService extends StateCacheService<WorkPackageResour
updateWorkPackageList(list:WorkPackageResourceInterface[]) { updateWorkPackageList(list:WorkPackageResourceInterface[]) {
for (var wp of list) { for (var wp of list) {
const workPackageId = getWorkPackageId(wp.id); const workPackageId = getWorkPackageId(wp.id);
const wpState = this.states.workPackages.get(workPackageId); const wpState = this.multiState.get(workPackageId);
const lastValue = wpState.value; const lastValue = wpState.value;
const wpForPublish = lastValue && lastValue.dirty const wpForPublish = lastValue && lastValue.dirty
? lastValue // dirty, use current wp ? lastValue // dirty, use current wp
@ -146,7 +146,7 @@ export class WorkPackageCacheService extends StateCacheService<WorkPackageResour
this.apiWorkPackages.loadWorkPackageById(id, true) this.apiWorkPackages.loadWorkPackageById(id, true)
.then((workPackage:WorkPackageResourceInterface) => { .then((workPackage:WorkPackageResourceInterface) => {
this.schemaCacheService.ensureLoaded(workPackage).then(() => { this.schemaCacheService.ensureLoaded(workPackage).then(() => {
this.updateValue(id, workPackage); this.multiState.get(id).putValue(workPackage);
resolve(workPackage); resolve(workPackage);
}, reject); }, reject);
}, reject); }, reject);

@ -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();
}
}

@ -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);
});
}
}

@ -1,16 +1,15 @@
import {InsertMode, ViewMode} from './wp-attachments-formattable.enums'; import {InsertMode, ViewMode} from './wp-attachments-formattable.enums';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; 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 {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service';
import {openprojectModule} from '../../../angular-modules'; import {openprojectModule} from '../../../angular-modules';
import {WorkPackageCacheService} from '../work-package-cache.service'; import {WorkPackageCacheService} from '../work-package-cache.service';
import {MarkupModel} from './models/markup-model'; import {MarkupModel} from './models/markup-model';
import {EditorModel} from './models/editor-model'; import {EditorModel} from './models/editor-model';
import {PasteModel} from './models/paste-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 {DropModel} from './models/drop-model';
import {SingleAttachmentModel} from './models/single-attachment'; 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 { export class WpAttachmentsFormattableController {
constructor(protected $scope:ng.IScope, constructor(protected $scope:ng.IScope,
@ -18,7 +17,6 @@ export class WpAttachmentsFormattableController {
protected $rootScope:ng.IRootScopeService, protected $rootScope:ng.IRootScopeService,
protected $location:ng.ILocationService, protected $location:ng.ILocationService,
protected wpCacheService:WorkPackageCacheService, protected wpCacheService:WorkPackageCacheService,
protected wpEditing:WorkPackageEditingService,
protected $timeout:ng.ITimeoutService, protected $timeout:ng.ITimeoutService,
protected $q:ng.IQService, protected $q:ng.IQService,
protected $state:ng.ui.IStateService, protected $state:ng.ui.IStateService,
@ -43,7 +41,7 @@ export class WpAttachmentsFormattableController {
const [, editor] = this.getEditor(); const [, editor] = this.getEditor();
const originalEvent = (evt.originalEvent as DragEvent); 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, const dropData:DropModel = new DropModel(this.$location,
originalEvent.dataTransfer, originalEvent.dataTransfer,
workPackage); workPackage);
@ -87,7 +85,7 @@ export class WpAttachmentsFormattableController {
* Get the editor model for the current view mode. * Get the editor model for the current view mode.
* This is either the editing model (open textarea field), or the closed field model. * 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'); const textarea:ng.IAugmentedJQuery = this.$element.find('textarea');
let viewMode; let viewMode;
@ -98,20 +96,19 @@ export class WpAttachmentsFormattableController {
model = new EditorModel(textarea, new MarkupModel()); model = new EditorModel(textarea, new MarkupModel());
} else { } else {
viewMode = ViewMode.SHOW; 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]; return [viewMode, model];
} }
protected uploadAndInsert(files:File[], model:EditorModel | FieldModel) { protected uploadAndInsert(files:File[], model:EditorModel | WorkPackageFieldModel) {
const workPackage:WorkPackageResourceInterface = this.$scope.workPackage; const wp = this.$scope.workPackage as WorkPackageResourceInterface;
if (wp.isNew) {
if (workPackage.isNew) { return this.insertDelayedAttachments(files, model, wp);
return this.insertDelayedAttachments(files, model, workPackage);
} }
workPackage wp
.uploadAttachments(files) .uploadAttachments(files)
.then(attachments => attachments.elements) .then(attachments => attachments.elements)
.then((updatedAttachments:any) => { .then((updatedAttachments:any) => {
@ -206,28 +203,18 @@ export class WpAttachmentsFormattableController {
}; };
} }
interface IAttachmentScope extends ng.IScope {
workPackage:WorkPackageResourceInterface;
}
function wpAttachmentsFormattable() { function wpAttachmentsFormattable() {
return { return {
bindToController: true, bindToController: true,
controller: WpAttachmentsFormattableController, controller: WpAttachmentsFormattableController,
link: (scope:IAttachmentScope, link: (scope:ng.IScope,
element:ng.IAugmentedJQuery, element:ng.IAugmentedJQuery,
attrs:ng.IAttributes, attrs:ng.IAttributes,
controllers:[WorkPackageSingleViewController]) => { 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.workPackage = controllers[0].workPackage;
scope.attribute = scope.$eval(attrs.fieldName);
}, },
require: ['?^wpSingleView', '?^wpEditForm'], require: ['^wpSingleView'],
restrict: 'A' restrict: 'A'
}; };
} }

@ -73,7 +73,17 @@
</div> </div>
<div ng-if="!descriptor.multiple" <div ng-if="!descriptor.multiple"
class="attributes-key-value--value-container"> class="attributes-key-value--value-container">
<wp-edit-field work-package-id="$ctrl.workPackage.id" field-name="descriptor.name"></wp-edit-field>
<wp-edit-field ng-if="descriptor.field.isFormattable"
class="wp-edit-formattable-field"
work-package-id="$ctrl.workPackage.id"
field-name="descriptor.name"
wp-attachments-formattable>
</wp-edit-field>
<wp-edit-field ng-if="!descriptor.field.isFormattable"
work-package-id="$ctrl.workPackage.id"
field-name="descriptor.name">
</wp-edit-field>
</div> </div>
<div <div
class="attributes-key-value--key" class="attributes-key-value--key"

@ -173,7 +173,7 @@ export class WorkPackageSingleViewController {
name: fieldName, name: fieldName,
label: field.label, label: field.label,
multiple: false, multiple: false,
spanAll: field.isLargeField, spanAll: field.isFormattable,
field: field field: field
}); });
}); });

@ -52,7 +52,7 @@ export class FormattableDisplayField extends DisplayField {
element.appendChild(span); element.appendChild(span);
} }
public get isLargeField():boolean { public get isFormattable():boolean {
return true; return true;
} }

@ -35,7 +35,7 @@ export class DisplayField extends Field {
public I18n: op.I18n; public I18n: op.I18n;
public mode:string|null = null; public mode:string|null = null;
public get isLargeField():boolean { public get isFormattable():boolean {
return false; return false;
} }

@ -15,7 +15,7 @@
<div class="inplace-edit--preview" ng-show="vm.field.isPreview && !vm.field.isBusy"> <div class="inplace-edit--preview" ng-show="vm.field.isPreview && !vm.field.isBusy">
<span bind-unescaped-html="vm.field.previewHtml"></span> <span bind-unescaped-html="vm.field.previewHtml"></span>
</div> </div>
<div ng-if="vm.field.supportsAttachments && !vm.field.isPreview" <div ng-if="vm.field.isFormattable && !vm.field.isPreview"
class="wp-edit-field-attachment-label"> class="wp-edit-field-attachment-label">
<span ng-bind="vm.field.text.attachmentLabel"></span> <span ng-bind="vm.field.text.attachmentLabel"></span>
</div> </div>

@ -65,8 +65,8 @@ export class WikiTextareaEditField extends EditField {
}; };
} }
public get supportsAttachments() { public get isFormattable() {
return this.name === 'description'; return true;
} }
public isEmpty(): boolean { public isEmpty(): boolean {

@ -33,6 +33,8 @@ import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing
import {WorkPackageEditForm} from '../../wp-edit-form/work-package-edit-form'; import {WorkPackageEditForm} from '../../wp-edit-form/work-package-edit-form';
import {SingleViewEditContext} from '../../wp-edit-form/single-view-edit-context'; import {SingleViewEditContext} from '../../wp-edit-form/single-view-edit-context';
import {input} from 'reactivestates'; 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 { export class WorkPackageEditFieldGroupController {
public workPackageId:string; public workPackageId:string;
@ -65,6 +67,13 @@ export class WorkPackageEditFieldGroupController {
} }
public $onInit() { 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) { if (this.inEditMode) {
this.start(); this.start();
} }
@ -82,6 +91,11 @@ export class WorkPackageEditFieldGroupController {
if (form && form.editMode) { if (form && form.editMode) {
field.activateOnForm(form, true); 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); this.wpEditing.stopEditing(this.workPackageId);
} }
private update(field:WorkPackageEditFieldController, wp:WorkPackageResourceInterface) {
field.workPackage = wp;
field.render();
}
private get editContext() { private get editContext() {
return new SingleViewEditContext(this); return new SingleViewEditContext(this);
} }

@ -70,13 +70,6 @@ export class WorkPackageEditFieldController {
public $onInit() { public $onInit() {
this.wpEditFieldGroup.register(this); this.wpEditFieldGroup.register(this);
this.states.workPackages.get(this.workPackageId)
.values$()
.takeUntil(scopeDestroyed$(this.$scope))
.subscribe((wp) => {
this.workPackage = wp;
this.render();
});
} }
public render() { public render() {

@ -31,7 +31,8 @@ module.exports = function() {
var uploadProgressController = function(scope) { var uploadProgressController = function(scope) {
scope.upload.progress(function(details) { 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) { if (details.lengthComputable) {
scope.value = Math.round(details.loaded / details.total * 100); scope.value = Math.round(details.loaded / details.total * 100);
} else { } else {

Loading…
Cancel
Save