diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index 847b7ab968..eab3c7052b 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -1,7 +1,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { MemoizedSelector, select, Store } from '@ngrx/store'; -import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs'; +import { combineLatest as observableCombineLatest } from 'rxjs'; import { Observable } from 'rxjs/internal/Observable'; import { distinctUntilChanged, filter, map, startWith, switchMap, take, tap } from 'rxjs/operators'; import { @@ -138,10 +138,10 @@ export class RelationshipService extends DataService { * @param relationshipId The identifier of the relationship */ private refreshRelationshipItemsInCacheByRelationship(relationshipId: string) { - this.findById(relationshipId).pipe( + this.findById(relationshipId, followLink('leftItem'), followLink('rightItem')).pipe( getSucceededRemoteData(), getRemoteDataPayload(), - switchMap((rel: Relationship) => combineLatest( + switchMap((rel: Relationship) => observableCombineLatest( rel.leftItem.pipe(getSucceededRemoteData(), getRemoteDataPayload()), rel.rightItem.pipe(getSucceededRemoteData(), getRemoteDataPayload()) ) @@ -160,7 +160,7 @@ export class RelationshipService extends DataService { private refreshRelationshipItemsInCache(item) { this.objectCache.remove(item._links.self.href); this.requestService.removeByHrefSubstring(item.uuid); - combineLatest( + observableCombineLatest( this.objectCache.hasBySelfLinkObservable(item._links.self.href), this.requestService.hasByHrefObservable(item.self) ).pipe( diff --git a/src/app/core/json-patch/builder/json-patch-operations-builder.ts b/src/app/core/json-patch/builder/json-patch-operations-builder.ts index c383d72f96..dc4b2f7052 100644 --- a/src/app/core/json-patch/builder/json-patch-operations-builder.ts +++ b/src/app/core/json-patch/builder/json-patch-operations-builder.ts @@ -35,13 +35,11 @@ export class JsonPatchOperationsBuilder { * A boolean representing if the value to be added is a plain text value */ add(path: JsonPatchOperationPathObject, value, first = false, plain = false) { - if ((typeof value === 'object' && hasValue(value.value)) || hasValue(value)) { - this.store.dispatch( - new NewPatchAddOperationAction( - path.rootElement, - path.subRootElement, - path.path, this.prepareValue(value, plain, first))); - } + this.store.dispatch( + new NewPatchAddOperationAction( + path.rootElement, + path.subRootElement, + path.path, this.prepareValue(value, plain, first))); } /** diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html index 93165c24cd..a7d4ca2d3f 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html @@ -1,7 +1,7 @@
-
- -
+ + +
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.html b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.html index 25c091d386..05f2813165 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.html +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.html @@ -1,7 +1,7 @@
-
- -
+ + +
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index 222e821bcf..3529736820 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -9,9 +9,7 @@
- - @@ -20,8 +18,7 @@
- -
+
- -
+
- - diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts index 2864688bb5..cb63491004 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts @@ -1,5 +1,14 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop'; -import { Component, EventEmitter, Input, NgZone, OnInit, Output, QueryList } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + EventEmitter, + Input, + NgZone, + OnInit, + Output, + QueryList +} from '@angular/core'; import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms'; import { DynamicFormArrayComponent, @@ -25,7 +34,7 @@ import { } from '../../../../../../core/shared/operators'; import { SubmissionObject } from '../../../../../../core/submission/models/submission-object.model'; import { SubmissionObjectDataService } from '../../../../../../core/submission/submission-object-data.service'; -import { hasNoValue, hasValue, isNotEmpty, isNull } from '../../../../../empty.util'; +import { hasNoValue, hasValue, isEmpty, isNotEmpty, isNull } from '../../../../../empty.util'; import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model'; import { Reorderable, @@ -41,6 +50,7 @@ import { ObjectCacheService } from '../../../../../../core/cache/object-cache.se import { RequestService } from '../../../../../../core/data/request.service'; import { SubmissionService } from '../../../../../../submission/submission.service'; import { AppState } from '../../../../../../app.reducer'; +import { followLink } from '../../../../../utils/follow-link-config.model'; @Component({ selector: 'ds-dynamic-form-array', @@ -69,6 +79,7 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent imple constructor(protected layoutService: DynamicFormLayoutService, protected validationService: DynamicFormValidationService, protected relationshipService: RelationshipService, + protected changeDetectorRef: ChangeDetectorRef, protected submissionObjectService: SubmissionObjectDataService, protected zone: NgZone, protected formService: DynamicFormService, @@ -80,7 +91,7 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent imple ngOnInit(): void { this.submissionObjectService - .findById(this.model.submissionId).pipe( + .findById(this.model.submissionId, followLink('item')).pipe( getSucceededRemoteData(), getRemoteDataPayload(), switchMap((submissionObject: SubmissionObject) => (submissionObject.item as Observable>) @@ -91,12 +102,12 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent imple ) ).subscribe((item) => { this.submissionItem = item; - this.updateReorderables(); + this.updateReorderables(false); }); } - private updateReorderables(): void { + private updateReorderables(shouldPropagateChanges = true): void { this.zone.runOutsideAngular(() => { let groups = this.model.groups.map((group, index) => [group, (this.control as any).controls[index]]); groups = [...groups, groups[0]]; @@ -114,7 +125,7 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent imple confidence: formFieldMetadataValue.confidence }); if (metadataValue.isVirtual) { - return this.relationshipService.findById(metadataValue.virtualValue) + return this.relationshipService.findById(metadataValue.virtualValue, followLink('leftItem')) .pipe( getSucceededRemoteData(), getRemoteDataPayload(), @@ -158,39 +169,54 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent imple observableCombineLatest(reorderable$arr) .subscribe((reorderables: Reorderable[]) => { - if (isNotEmpty(this.reorderables)) { - reorderables.forEach((newReorderable: Reorderable) => { - const match = this.reorderables.find((reo: Reorderable) => reo.getId() === newReorderable.getId()); - if (hasValue(match)) { - newReorderable.oldIndex = match.newIndex; - } - }) - } - this.reorderables = reorderables; - const updatedReorderables: Array> = []; - this.reorderables.forEach((reorderable: Reorderable, index: number) => { - if (reorderable.hasMoved) { - const prevIndex = reorderable.oldIndex; - const updatedReorderable = reorderable.update().pipe(take(1)); - updatedReorderables.push(updatedReorderable); - updatedReorderable.subscribe((v) => { - if (reorderable instanceof ReorderableFormFieldMetadataValue) { - const reoMD = reorderable as ReorderableFormFieldMetadataValue; - const mdl = Object.assign({}, reoMD.model, { value: reoMD.metadataValue }); - this.onChange({ - $event: { previousIndex: prevIndex }, - context: { index }, - control: reoMD.control, - group: this.group, - model: mdl, - type: DynamicFormControlEventType.Change - }); - } - }); + reorderables.forEach((newReorderable: Reorderable) => { + const match = this.reorderables.find((reo: Reorderable) => reo.getId() === newReorderable.getId()); + if (hasValue(match)) { + newReorderable.oldIndex = match.newIndex; + } else { + newReorderable.oldIndex = -1; } }); - observableCombineLatest(...updatedReorderables).pipe( - ).subscribe(() => this.submissionService.dispatchSave(this.model.submissionId)); + + this.reorderables = reorderables; + + if (shouldPropagateChanges) { + const updatedReorderables: Array> = []; + let hasMetadataField = false; + this.reorderables.forEach((reorderable: Reorderable, index: number) => { + if (reorderable.hasMoved) { + const prevIndex = reorderable.oldIndex; + const updatedReorderable = reorderable.update().pipe(take(1)); + updatedReorderables.push(updatedReorderable); + if (reorderable instanceof ReorderableFormFieldMetadataValue) { + hasMetadataField = true; + updatedReorderable.subscribe((v) => { + const reoMD = reorderable as ReorderableFormFieldMetadataValue; + const mdl = Object.assign({}, reoMD.model, { value: reoMD.metadataValue }); + this.onChange({ + $event: { previousIndex: prevIndex }, + context: { index }, + control: reoMD.control, + group: this.group, + model: mdl, + type: DynamicFormControlEventType.Change + }); + }); + } + } + }); + + observableCombineLatest(updatedReorderables).pipe( + ).subscribe(() => { + if (hasMetadataField && hasValue(this.model.relationshipConfig)) { + // if it's a mix between entities and regular metadata fields, + // we need to save after every operation, since they use different + // endpoints and otherwise they'll get out of sync. + this.submissionService.dispatchSave(this.model.submissionId); + } + this.changeDetectorRef.detectChanges(); + }); + } }); }) } @@ -199,15 +225,4 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent imple this.model.moveGroup(event.previousIndex, event.currentIndex - event.previousIndex); this.updateReorderables(); } - - onChange($event) { - let event = $event; - if (hasValue($event) && hasNoValue($event.context)) { - const context = Object.assign({}, $event.context, { index: this.reorderables.length }); - event = Object.assign({}, $event, { context }); - } else { - this.updateReorderables(); - } - super.onChange(event); - } } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts index 23193e0f8e..24f75801a3 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts @@ -19,6 +19,7 @@ import { ObjectCacheService } from '../../../../../core/cache/object-cache.servi import { RequestService } from '../../../../../core/data/request.service'; import { ServerSyncBufferActionTypes } from '../../../../../core/cache/server-sync-buffer.actions'; import { CommitPatchOperationsAction, JsonPatchOperationsActionTypes, PatchOperationsActions } from '../../../../../core/json-patch/json-patch-operations.actions'; +import { followLink } from '../../../../utils/follow-link-config.model'; const DEBOUNCE_TIME = 5000; @@ -162,10 +163,15 @@ export class RelationshipEffects { this.relationshipService.getRelationshipByItemsAndLabel(item1, item2, relationshipType).pipe( take(1), hasValueOperator(), + tap((v) => console.log('before delete', v)), mergeMap((relationship: Relationship) => this.relationshipService.deleteRelationship(relationship.id, 'none')), take(1), + tap((v) => console.log('before refresh', v)), switchMap(() => this.refreshWorkspaceItemInCache(submissionId)), - ).subscribe((submissionObject: SubmissionObject) => this.store.dispatch(new SaveSubmissionSectionFormSuccessAction(submissionId, [submissionObject], false))); + ).subscribe((submissionObject: SubmissionObject) => { + console.log('in subscribe', submissionObject); + this.store.dispatch(new SaveSubmissionSectionFormSuccessAction(submissionId, [submissionObject], false)) + }); } refreshWorkspaceItemInCache(submissionId: string): Observable { @@ -179,7 +185,7 @@ export class RelationshipEffects { ).pipe( filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC), take(1), - switchMap(() => this.submissionObjectService.findById(submissionId).pipe(getSucceededRemoteData(), getRemoteDataPayload()) as Observable) + switchMap(() => this.submissionObjectService.findById(submissionId, followLink('item')).pipe(getSucceededRemoteData(), getRemoteDataPayload()) as Observable) ) }) ); diff --git a/src/app/shared/form/builder/parsers/field-parser.ts b/src/app/shared/form/builder/parsers/field-parser.ts index 32f4b80f37..16b0a7a7ce 100644 --- a/src/app/shared/form/builder/parsers/field-parser.ts +++ b/src/app/shared/form/builder/parsers/field-parser.ts @@ -80,6 +80,9 @@ export abstract class FieldParser { model = this.modelFactory(fieldValue, false); if (!isFirstModelInArray) { model.hint = undefined; + if (Array.isArray(model.group)) { + model.group.forEach((group) => group.hint = undefined); + } } } setLayout(model, 'element', 'host', 'col'); diff --git a/src/app/submission/sections/form/section-form-operations.service.ts b/src/app/submission/sections/form/section-form-operations.service.ts index f7cf8f36dd..79489929ab 100644 --- a/src/app/submission/sections/form/section-form-operations.service.ts +++ b/src/app/submission/sections/form/section-form-operations.service.ts @@ -9,7 +9,15 @@ import { DynamicFormControlModel } from '@ng-dynamic-forms/core'; -import { hasValue, isNotEmpty, isNotNull, isNotUndefined, isNull, isUndefined } from '../../../shared/empty.util'; +import { + hasNoValue, + hasValue, + isNotEmpty, + isNotNull, + isNotUndefined, + isNull, + isUndefined +} from '../../../shared/empty.util'; import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { FormFieldPreviousValueObject } from '../../../shared/form/builder/models/form-field-previous-value-object'; import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; @@ -312,7 +320,7 @@ export class SectionFormOperationsService { } else if (this.formBuilder.isRelationGroup(event.model)) { // It's a relation model this.dispatchOperationsFromMap(this.getValueMap(value), pathCombiner, event, previousValue); - } else if (this.formBuilder.hasArrayGroupValue(event.model)) { + } else if (this.formBuilder.hasArrayGroupValue(event.model) && hasNoValue((event.model as any).relationshipConfig)) { // Model has as value an array, so dispatch an add operation with entire block of values this.operationsBuilder.add( pathCombiner.getPath(segmentedPath), @@ -327,10 +335,16 @@ export class SectionFormOperationsService { this.operationsBuilder.remove(pathCombiner.getPath(path)); } } else if (hasValue(event.$event) && hasValue(event.$event.previousIndex)) { - this.operationsBuilder.move( - pathCombiner.getPath(path), - pathCombiner.getPath(segmentedPath + '/' + event.$event.previousIndex).path - ) + if (event.$event.previousIndex < 0) { + this.operationsBuilder.add( + pathCombiner.getPath(segmentedPath), + value, true); + } else { + this.operationsBuilder.move( + pathCombiner.getPath(path), + pathCombiner.getPath(segmentedPath + '/' + event.$event.previousIndex).path + ) + } } else { // New value is not equal from the previous one, so dispatch a replace operation this.operationsBuilder.replace( diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index 5eca81e7a8..b708fcf2ab 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectorRef, Component, Inject, ViewChild } from '@angular/core'; import { DynamicFormControlEvent, DynamicFormControlModel } from '@ng-dynamic-forms/core'; -import { combineLatest, Observable, Subscription } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; import { distinctUntilChanged, filter, find, flatMap, map, switchMap, take, tap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { isEqual } from 'lodash'; @@ -32,13 +32,12 @@ import { SectionsService } from '../sections.service'; import { difference } from '../../../shared/object.util'; import { WorkspaceitemSectionFormObject } from '../../../core/submission/models/workspaceitem-section-form.model'; import { WorkspaceItem } from '../../../core/submission/models/workspaceitem.model'; -import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service'; -import { combineLatest as combineLatestObservable } from 'rxjs'; import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators'; import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; import { SubmissionObjectDataService } from '../../../core/submission/submission-object-data.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { RequestService } from '../../../core/data/request.service'; +import { followLink } from '../../../shared/utils/follow-link-config.model'; /** * This component represents a section that contains a Form. @@ -166,19 +165,19 @@ export class SubmissionSectionformComponent extends SectionModelComponent { map((configData: ConfigData) => configData.payload), tap((config: SubmissionFormsModel) => this.formConfig = config), flatMap(() => - combineLatestObservable( + observableCombineLatest( this.sectionService.getSectionData(this.submissionId, this.sectionData.id), this.submissionObjectService.getHrefByID(this.submissionId).pipe(take(1)).pipe( switchMap((href: string) => { this.objectCache.remove(href); this.requestService.removeByHrefSubstring(this.submissionId); - return combineLatest( + return observableCombineLatest( this.objectCache.hasBySelfLinkObservable(href), this.requestService.hasByHrefObservable(href) ).pipe( filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC), take(1), - switchMap(() => this.submissionObjectService.findById(this.submissionId).pipe(getSucceededRemoteData(), getRemoteDataPayload()) as Observable) + switchMap(() => this.submissionObjectService.findById(this.submissionId, followLink('item')).pipe(getSucceededRemoteData(), getRemoteDataPayload()) as Observable) ) }) )