diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 814adba9a734788b0d5e55847da76412292948ea..a4f90149803324b5d06acc6161ef69dbed824900 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -42,6 +42,7 @@ "head": "Collections of this Community" }, "edit": { + "head": "Edit collction", "name": "Name", "description": "Short Description", "introductory": "Introductory text (HTML)", diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.ts b/src/app/+collection-page/create-collection-page/create-collection-page.component.ts index a8ee329a0f496941a2f93080ab621efc2c442b86..d934e1d7a0c6cf7b4c324432ea38bfb7b30acb6d 100644 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.ts +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.ts @@ -38,7 +38,7 @@ export class CreateCollectionPageComponent { onSubmit(data: any) { this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { - const collection = Object.assign(new NormalizedCollection(), { + const collection = Object.assign(new Collection(), { name: data.name, metadata: [ { key: 'dc.description', value: data.introductory }, diff --git a/src/app/+community-page/community-form/community-form.component.html b/src/app/+community-page/community-form/community-form.component.html index 578e2a0d6acc4459b5bc3e5e5e77ffa86baf7543..637506f86d5f304b8b4682160cb01756f9633c04 100644 --- a/src/app/+community-page/community-form/community-form.component.html +++ b/src/app/+community-page/community-form/community-form.component.html @@ -1,27 +1,4 @@ -<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="row" action="/"> - <div class="col-12 form-group"> - <label for="community-name" class="font-weight-bold">{{ 'community.edit.name' | translate }}</label> - <input type="text" [(ngModel)]="name" name="name" id="community-name" class="form-control" [ngClass]="{'is-invalid' : !name && nameRequiredError}" aria-label="Community Name" /> - <div class="invalid-feedback">{{ 'community.edit.required.name' | translate }}</div> - </div> - <div class="col-12 form-group"> - <label for="community-description" class="font-weight-bold">{{ 'community.edit.description' | translate }}</label> - <input type="text" [(ngModel)]="description" name="description" id="community-description" class="form-control" aria-label="Community Short Description" /> - </div> - <div class="col-12 form-group"> - <label for="community-introductory" class="font-weight-bold">{{ 'community.edit.introductory' | translate }}</label> - <textarea [(ngModel)]="introductory" name="introductory" id="community-introductory" class="form-control" aria-label="Community Introductory Text" rows="6" ></textarea> - </div> - <div class="col-12 form-group"> - <label for="community-copyright" class="font-weight-bold">{{ 'community.edit.copyright' | translate }}</label> - <textarea [(ngModel)]="copyright" name="copyright" id="community-copyright" class="form-control" aria-label="Community Copyright Text" rows="6" ></textarea> - </div> - <div class="col-12 form-group"> - <label for="community-news" class="font-weight-bold">{{ 'community.edit.news' | translate }}</label> - <textarea [(ngModel)]="news" name="news" id="community-news" class="form-control" aria-label="Community News" rows="6" ></textarea> - </div> - <div class="col-12 form-group"> - <button type="button" class="btn btn-secondary" id="community-cancel" (click)="cancel()">{{ 'community.edit.cancel' | translate }}</button> - <button type="submit" class="btn btn-secondary" id="community-submit">{{ 'community.edit.submit' | translate }}</button> - </div> -</form> +<ds-form *ngIf="formModel" #formRef="formComponent" + [formId]="'community-form-id'" + [formModel]="formModel" (submit)="onSubmit($event)"></ds-form> + diff --git a/src/app/+community-page/community-form/community-form.component.ts b/src/app/+community-page/community-form/community-form.component.ts index d7797028b7cfb339e950fc731a43cf478a81cb2a..fd0b49014661a8be03ebaa8306c9c1b86c608cde 100644 --- a/src/app/+community-page/community-form/community-form.component.ts +++ b/src/app/+community-page/community-form/community-form.component.ts @@ -1,39 +1,93 @@ -import { Component, EventEmitter, Output } from '@angular/core'; -import { isNotEmpty } from '../../shared/empty.util'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Location } from '@angular/common'; +import { + DynamicFormService, + DynamicInputModel, + DynamicTextAreaModel +} from '@ng-dynamic-forms/core'; +import { FormGroup } from '@angular/forms'; +import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; +import { Community } from '../../core/shared/community.model'; +import { ResourceType } from '../../core/shared/resource-type'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-community-form', styleUrls: ['./community-form.component.scss'], templateUrl: './community-form.component.html' }) -export class CommunityFormComponent { +export class CommunityFormComponent implements OnInit { - name: string; - description: string; - introductory: string; - copyright: string; - news: string; + @Input() community: Community = new Community(); + formModel: DynamicFormControlModel[] = [ + new DynamicInputModel({ + id: 'title', + name: 'dc.title', + label: 'Name', + required: true, + validators: { + required: null + }, + errorMessages: { + required: 'Please enter a name for this title' + } + }), + new DynamicTextAreaModel({ + id: 'description', + name: 'dc.description', + label: 'Introductory text (HTML)', + }), + new DynamicTextAreaModel({ + id: 'abstract', + name: 'dc.description.abstract', + label: 'Short Description', + }), + new DynamicTextAreaModel({ + id: 'rights', + name: 'dc.rights', + label: 'Copyright text (HTML)', + }), + new DynamicTextAreaModel({ + id: 'tableofcontents', + name: 'dc.description.tableofcontents', + label: 'News (HTML)', + }), + ]; - nameRequiredError = false; + formGroup: FormGroup; - @Output() submitted: EventEmitter<any> = new EventEmitter(); + @Output() submitForm: EventEmitter<any> = new EventEmitter(); - public constructor(private location: Location) { + public constructor(private location: Location, private formService: DynamicFormService) { + } + ngOnInit(): void { + this.formModel.forEach( + (fieldModel: DynamicInputModel) => { + fieldModel.value = this.community.findMetadata(fieldModel.name); + } + ); + this.formGroup = this.formService.createFormGroup(this.formModel); } - onSubmit(data: any) { - if (isNotEmpty(data.name)) { - this.submitted.emit(data); - this.nameRequiredError = false; - } else { - this.nameRequiredError = true; - } + onSubmit(event: Event) { + event.stopPropagation(); + const metadata = this.formModel.map( + (fieldModel: DynamicInputModel) => { + return { key: fieldModel.name, value: fieldModel.value } + } + ); + const filteredOldMetadata = this.community.metadata.filter((filter) => !metadata.map((md) => md.key).includes(filter.key)); + const filteredNewMetadata = metadata.filter((md) => isNotEmpty(md.value)); + const newMetadata = [...filteredOldMetadata, ...filteredNewMetadata]; + const updatedCommunity = Object.assign({}, this.community, { + metadata: newMetadata, + type: ResourceType.Community + }); + this.submitForm.emit(updatedCommunity); } cancel() { this.location.back(); } - } diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index 17889d6e487602a498bc1d83a00d427243bfb2ea..8fcc2cde6f5feb4c41786eeb49ec84b51f228b2c 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -5,13 +5,23 @@ import { CommunityPageComponent } from './community-page.component'; import { CommunityPageResolver } from './community-page.resolver'; import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; +import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component'; @NgModule({ imports: [ RouterModule.forChild([ { path: 'create', component: CreateCommunityPageComponent, - canActivate: [AuthenticatedGuard] }, + canActivate: [AuthenticatedGuard] + }, + { path: ':id/edit', + pathMatch: 'full', + component: EditCommunityPageComponent, + canActivate: [AuthenticatedGuard], + resolve: { + community: CommunityPageResolver + } + }, { path: ':id', component: CommunityPageComponent, diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html index 1bf322a68892bb17810697a818e42bbff3aec5ff..52bc9fe928663e0342698568349571bcd8782e79 100644 --- a/src/app/+community-page/community-page.component.html +++ b/src/app/+community-page/community-page.component.html @@ -28,6 +28,8 @@ [community]="communityPayload"></ds-community-page-sub-collection-list> </div> </div> + <a [routerLink]="'edit'">Edit</a> + <ds-error *ngIf="communityRD?.hasFailed" message="{{'error.community' | translate}}"></ds-error> <ds-loading *ngIf="communityRD?.isLoading" message="{{'loading.community' | translate}}"></ds-loading> diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index ce260aefc018d58d70b4d8fd198f6e06c962af07..bfaac33c1c39f36eeb5eeae510b42d78bc33f049 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -24,8 +24,6 @@ import { hasValue } from '../shared/empty.util'; export class CommunityPageComponent implements OnInit, OnDestroy { communityRD$: Observable<RemoteData<Community>>; logoRD$: Observable<RemoteData<Bitstream>>; - - private subs: Subscription[] = []; constructor( @@ -42,14 +40,9 @@ export class CommunityPageComponent implements OnInit, OnDestroy { map((rd: RemoteData<Community>) => rd.payload), filter((community: Community) => hasValue(community)), mergeMap((community: Community) => community.logo)); - - } ngOnDestroy(): void { this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } - - - } diff --git a/src/app/+community-page/community-page.module.ts b/src/app/+community-page/community-page.module.ts index 292e6aaf9ca306608d0faadd218c35a13fe9cbaf..23050f40db6d90961d5759db0db9a7e1a828613f 100644 --- a/src/app/+community-page/community-page.module.ts +++ b/src/app/+community-page/community-page.module.ts @@ -8,6 +8,7 @@ import { CommunityPageSubCollectionListComponent } from './sub-collection-list/c import { CommunityPageRoutingModule } from './community-page-routing.module'; import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; import { CommunityFormComponent } from './community-form/community-form.component'; +import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component'; @NgModule({ imports: [ @@ -19,6 +20,7 @@ import { CommunityFormComponent } from './community-form/community-form.componen CommunityPageComponent, CommunityPageSubCollectionListComponent, CreateCommunityPageComponent, + EditCommunityPageComponent, CommunityFormComponent ] }) diff --git a/src/app/+community-page/create-community-page/create-community-page.component.html b/src/app/+community-page/create-community-page/create-community-page.component.html index 35f1deaa73339731d70a4a239c1d037122e64369..bb0f5b83af30c9db1ea9ec024cd15f447c1daf31 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.html +++ b/src/app/+community-page/create-community-page/create-community-page.component.html @@ -7,5 +7,5 @@ </ng-container> </div> </div> - <ds-community-form (submitted)="onSubmit($event)"></ds-community-form> + <ds-community-form (submitForm)="onSubmit($event)"></ds-community-form> </div> diff --git a/src/app/+community-page/create-community-page/create-community-page.component.spec.ts b/src/app/+community-page/create-community-page/create-community-page.component.spec.ts index 6c5eb5f59155b3f58b963f7a2e625c71ef41fade..e76b10bde67d984dffef91ac1337e3730ab6cdd8 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.spec.ts +++ b/src/app/+community-page/create-community-page/create-community-page.component.spec.ts @@ -8,12 +8,10 @@ import { Observable } from 'rxjs/Observable'; import { RemoteData } from '../../core/data/remote-data'; import { Community } from '../../core/shared/community.model'; import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; -import { BrowserModule } from '@angular/platform-browser'; import { SharedModule } from '../../shared/shared.module'; import { CommonModule } from '@angular/common'; import { CommunityFormComponent } from '../community-form/community-form.component'; import { RouterTestingModule } from '@angular/router/testing'; -import { RequestError } from '../../core/data/request.models'; describe('CreateCommunityPageComponent', () => { let comp: CreateCommunityPageComponent; diff --git a/src/app/+community-page/create-community-page/create-community-page.component.ts b/src/app/+community-page/create-community-page/create-community-page.component.ts index 0d97f2402891aba5b17f045e075114a09db41f4b..5373abfe2257f3d5916080f78f66b30db4e36164 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.ts +++ b/src/app/+community-page/create-community-page/create-community-page.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { Community } from '../../core/shared/community.model'; import { CommunityDataService } from '../../core/data/community-data.service'; import { Observable } from 'rxjs'; @@ -7,55 +7,46 @@ import { Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { take } from 'rxjs/operators'; -import { ResourceType } from '../../core/shared/resource-type'; -import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { map, take } from 'rxjs/operators'; +import { getSucceededRemoteData } from '../../core/shared/operators'; @Component({ selector: 'ds-create-community', styleUrls: ['./create-community-page.component.scss'], templateUrl: './create-community-page.component.html' }) -export class CreateCommunityPageComponent { +export class CreateCommunityPageComponent implements OnInit { public parentUUID$: Observable<string>; - public communityRDObs: Observable<RemoteData<Community>>; + public parentRD$: Observable<RemoteData<Community>>; public constructor( private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router ) { + + } + + ngOnInit(): void { this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); - this.parentUUID$.subscribe((uuid: string) => { - if (isNotEmpty(uuid)) { - this.communityRDObs = this.communityDataService.findById(uuid); + this.parentUUID$.subscribe((parentID: string) => { + if (isNotEmpty(parentID)) { + this.parentRD$ = this.communityDataService.findById(parentID); } }); } - onSubmit(data: any) { + onSubmit(community: Community) { this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { - const community = Object.assign(new NormalizedCommunity(), { - name: data.name, - metadata: [ - { key: 'dc.description', value: data.introductory }, - { key: 'dc.description.abstract', value: data.description }, - { key: 'dc.rights', value: data.copyright } - // TODO: metadata for news - ], - type: ResourceType.Community - }); - this.communityDataService.create(community, uuid).pipe(take(1)).subscribe((rd: RemoteData<DSpaceObject>) => { - if (rd.hasSucceeded) { - if (uuid) { - this.router.navigate(['communities', uuid]); - } else { - this.router.navigate([]); - } - } + this.communityDataService.create(community, uuid) + .pipe(getSucceededRemoteData()) + .subscribe((communityRD: RemoteData<Community>) => { + const newUUID = communityRD.payload.uuid; + this.router.navigate(['/communities/' + newUUID]); }); }); } + } diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.html b/src/app/+community-page/edit-community-page/edit-community-page.component.html new file mode 100644 index 0000000000000000000000000000000000000000..eb9f797b3d88dbd9e3ad088c075fcfa0f1427ef6 --- /dev/null +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.html @@ -0,0 +1,11 @@ +<div class="container"> + <div class="row"> + <div class="col-12 pb-4"> + <ng-container *ngVar="(parentUUID$ | async)?.payload as parent"> + <h2 *ngIf="!parent" id="header" class="border-bottom pb-2">{{ 'community.edit.head' | translate }}</h2> + <h2 *ngIf="parent" id="sub-header" class="border-bottom pb-2">{{ 'community.edit.sub-head' | translate:{ parent: parent.name } }}</h2> + </ng-container> + </div> + </div> + <ds-community-form (submitForm)="onSubmit($event)" [community]="(communityRD$ | async)?.payload"></ds-community-form> +</div> diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.scss b/src/app/+community-page/edit-community-page/edit-community-page.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.scss @@ -0,0 +1 @@ + diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c5eb5f59155b3f58b963f7a2e625c71ef41fade --- /dev/null +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts @@ -0,0 +1,90 @@ +import { CreateCommunityPageComponent } from './create-community-page.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { RouteService } from '../../shared/services/route.service'; +import { Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../../core/data/remote-data'; +import { Community } from '../../core/shared/community.model'; +import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; +import { BrowserModule } from '@angular/platform-browser'; +import { SharedModule } from '../../shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { CommunityFormComponent } from '../community-form/community-form.component'; +import { RouterTestingModule } from '@angular/router/testing'; +import { RequestError } from '../../core/data/request.models'; + +describe('CreateCommunityPageComponent', () => { + let comp: CreateCommunityPageComponent; + let fixture: ComponentFixture<CreateCommunityPageComponent>; + let communityDataService: CommunityDataService; + let routeService: RouteService; + let router: Router; + + const community = Object.assign(new Community(), { + uuid: 'a20da287-e174-466a-9926-f66b9300d347', + name: 'test community' + }); + + const newCommunity = Object.assign(new Community(), { + uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', + name: 'new community' + }); + + const communityDataServiceStub = { + findById: (uuid) => Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { + uuid: uuid, + name: community.name + }))), + create: (com, uuid?) => Observable.of(new RemoteData(false, false, true, undefined, newCommunity)) + }; + const routeServiceStub = { + getQueryParameterValue: (param) => Observable.of(community.uuid) + }; + const routerStub = { + navigate: (commands) => commands + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [CreateCommunityPageComponent, CommunityFormComponent], + providers: [ + { provide: CommunityDataService, useValue: communityDataServiceStub }, + { provide: RouteService, useValue: routeServiceStub }, + { provide: Router, useValue: routerStub } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateCommunityPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + communityDataService = (comp as any).communityDataService; + routeService = (comp as any).routeService; + router = (comp as any).router; + }); + + describe('onSubmit', () => { + const data = { + name: 'test' + }; + + it('should navigate when successful', () => { + spyOn(router, 'navigate'); + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should not navigate on failure', () => { + spyOn(router, 'navigate'); + spyOn(communityDataService, 'create').and.returnValue(Observable.of(new RemoteData(true, true, false, undefined, newCommunity))); + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigate).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..58805af80acd1b28bdd26ad60051062147582716 --- /dev/null +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.ts @@ -0,0 +1,46 @@ +import { Component } from '@angular/core'; +import { Community } from '../../core/shared/community.model'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { Observable } from 'rxjs'; +import { RouteService } from '../../shared/services/route.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RemoteData } from '../../core/data/remote-data'; +import { isNotEmpty } from '../../shared/empty.util'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { first, map, take, tap } from 'rxjs/operators'; +import { ResourceType } from '../../core/shared/resource-type'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { getSucceededRemoteData } from '../../core/shared/operators'; + +@Component({ + selector: 'ds-edit-community', + styleUrls: ['./edit-community-page.component.scss'], + templateUrl: './edit-community-page.component.html' +}) +export class EditCommunityPageComponent { + + public parentUUID$: Observable<string>; + public parentRD$: Observable<RemoteData<Community>>; + public communityRD$: Observable<RemoteData<Community>>; + + public constructor( + private communityDataService: CommunityDataService, + private routeService: RouteService, + private router: Router, + private route: ActivatedRoute + ) { + } + + ngOnInit(): void { + this.communityRD$ = this.route.data.pipe(first(), map((data) => data.community)); + } + + onSubmit(community: Community) { + this.communityDataService.update(community) + .pipe(getSucceededRemoteData()) + .subscribe((communityRD: RemoteData<Community>) => { + const newUUID = communityRD.payload.uuid; + this.router.navigate(['/communities/' + newUUID]); + }); + } +} diff --git a/src/app/core/cache/builders/data-build.service.ts b/src/app/core/cache/builders/data-build.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ba3ebee0c75e13575e089037a557835ad089b84 --- /dev/null +++ b/src/app/core/cache/builders/data-build.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { NormalizedObject } from '../models/normalized-object.model'; +import { CacheableObject } from '../object-cache.reducer'; +import { getRelationships } from './build-decorators'; +import { NormalizedObjectFactory } from '../models/normalized-object-factory'; +import { map, take } from 'rxjs/operators'; +import { hasValue, isNotEmpty } from '../../../shared/empty.util'; +import { PaginatedList } from '../../data/paginated-list'; + +export function isRestDataObject(halObj: any) { + return isNotEmpty(halObj._links) && hasValue(halObj._links.self); +} + +export function isRestPaginatedList(halObj: any) { + return hasValue(halObj.page) && hasValue(halObj._embedded); +} + +export function isPaginatedList(halObj: any) { + return hasValue(halObj.page) && hasValue(halObj.pageInfo); +} + +@Injectable() +export class DataBuildService { + normalize<TDomain extends CacheableObject, TNormalized extends NormalizedObject>(domainModel: TDomain): TNormalized { + const normalizedConstructor = NormalizedObjectFactory.getConstructor(domainModel.type); + const relationships = getRelationships(normalizedConstructor) || []; + + const normalizedModel = Object.assign({}, domainModel) as any; + relationships.forEach((key: string) => { + if (hasValue(domainModel[key])) { + domainModel[key] = undefined; + } + }); + return normalizedModel; + } +} diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 1dc255f26e91a3d9a6ab9ba012527260c6942de4..7b51526432dd795f699cdd6bc237a1b799e58812 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -51,10 +51,7 @@ export class RemoteDataBuildService { const requestEntry$ = observableRace( href$.pipe(getRequestFromRequestHref(this.requestService)), requestUUID$.pipe(getRequestFromRequestUUID(this.requestService)), - ).pipe( - take(1) ); - // always use self link if that is cached, only if it isn't, get it via the response. const payload$ = observableCombineLatest( diff --git a/src/app/core/cache/models/normalized-community.model.ts b/src/app/core/cache/models/normalized-community.model.ts index 4ab2408a53f3fedf25b75c07794b98735d1f4b40..e915d2f50a9175d2baba8e92e5ad47e185eb5d9d 100644 --- a/src/app/core/cache/models/normalized-community.model.ts +++ b/src/app/core/cache/models/normalized-community.model.ts @@ -1,4 +1,4 @@ -import { autoserialize, inheritSerialization } from 'cerialize'; +import { autoserialize, deserialize, inheritSerialization, serialize } from 'cerialize'; import { NormalizedDSpaceObject } from './normalized-dspace-object.model'; import { Community } from '../../shared/community.model'; @@ -21,32 +21,32 @@ export class NormalizedCommunity extends NormalizedDSpaceObject { /** * The Bitstream that represents the logo of this Community */ - @autoserialize + @deserialize @relationship(ResourceType.Bitstream, false) logo: string; /** * An array of Communities that are direct parents of this Community */ - @autoserialize + @deserialize @relationship(ResourceType.Community, true) parents: string[]; /** * The Community that owns this Community */ - @autoserialize + @deserialize @relationship(ResourceType.Community, false) owner: string; /** * List of Collections that are owned by this Community */ - @autoserialize + @deserialize @relationship(ResourceType.Collection, true) collections: string[]; - @autoserialize + @deserialize @relationship(ResourceType.Community, true) subcommunities: string[]; diff --git a/src/app/core/cache/models/normalized-dspace-object.model.ts b/src/app/core/cache/models/normalized-dspace-object.model.ts index 92174c40f778af9aebc62f6f89bb155ab9e054d3..efdfa6dd741a7bd0f9cf76025a759e5f4d645dfb 100644 --- a/src/app/core/cache/models/normalized-dspace-object.model.ts +++ b/src/app/core/cache/models/normalized-dspace-object.model.ts @@ -1,4 +1,4 @@ -import { autoserialize, autoserializeAs } from 'cerialize'; +import { autoserialize, autoserializeAs, deserialize, serialize } from 'cerialize'; import { DSpaceObject } from '../../shared/dspace-object.model'; import { Metadatum } from '../../shared/metadatum.model'; @@ -45,12 +45,6 @@ export class NormalizedDSpaceObject extends NormalizedObject { @autoserialize type: ResourceType; - /** - * The name for this DSpaceObject - */ - @autoserialize - name: string; - /** * An array containing all metadata of this DSpaceObject */ @@ -60,13 +54,13 @@ export class NormalizedDSpaceObject extends NormalizedObject { /** * An array of DSpaceObjects that are direct parents of this DSpaceObject */ - @autoserialize + @deserialize parents: string[]; /** * The DSpaceObject that owns this DSpaceObject */ - @autoserialize + @deserialize owner: string; /** @@ -75,7 +69,7 @@ export class NormalizedDSpaceObject extends NormalizedObject { * Repeated here to make the serialization work, * inheritSerialization doesn't seem to work for more than one level */ - @autoserialize + @deserialize _links: { [name: string]: string } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index dcbdbd00492bec714e15c6011b911e1110bb3048..21917438715ad3b82d43e6295c77e0fb0b42355e 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -63,6 +63,8 @@ import { NotificationsService } from '../shared/notifications/notifications.serv import { UploaderService } from '../shared/uploader/uploader.service'; import { BrowseItemsResponseParsingService } from './data/browse-items-response-parsing-service'; import { DSpaceObjectDataService } from './data/dspace-object-data.service'; +import { DataBuildService } from './cache/builders/data-build.service'; +import { DSOUpdateComparator } from './data/dso-update-comparator'; const IMPORTS = [ CommonModule, @@ -99,6 +101,7 @@ const PROVIDERS = [ ObjectCacheService, PaginationComponentOptions, RegistryService, + DataBuildService, RemoteDataBuildService, RequestService, EndpointMapResponseParsingService, @@ -126,6 +129,7 @@ const PROVIDERS = [ UploaderService, UUIDService, DSpaceObjectDataService, + DSOUpdateComparator, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/data/base-response-parsing.service.ts b/src/app/core/data/base-response-parsing.service.ts index eada156ce9392a66b842d5609f8e683fe893c1a0..d5c1c58296c08c6a552edd89e04474a3c26d34e0 100644 --- a/src/app/core/data/base-response-parsing.service.ts +++ b/src/app/core/data/base-response-parsing.service.ts @@ -8,15 +8,7 @@ import { GenericConstructor } from '../shared/generic-constructor'; import { PaginatedList } from './paginated-list'; import { ResourceType } from '../shared/resource-type'; import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; - -function isObjectLevel(halObj: any) { - return isNotEmpty(halObj._links) && hasValue(halObj._links.self); -} - -function isPaginatedResponse(halObj: any) { - return hasValue(halObj.page) && hasValue(halObj._embedded); -} - +import { isRestDataObject, isRestPaginatedList } from '../cache/builders/data-build.service'; /* tslint:disable:max-classes-per-file */ export abstract class BaseResponseParsingService { @@ -29,11 +21,11 @@ export abstract class BaseResponseParsingService { if (isNotEmpty(data)) { if (hasNoValue(data) || (typeof data !== 'object')) { return data; - } else if (isPaginatedResponse(data)) { + } else if (isRestPaginatedList(data)) { return this.processPaginatedList(data, requestUUID); } else if (Array.isArray(data)) { return this.processArray(data, requestUUID); - } else if (isObjectLevel(data)) { + } else if (isRestDataObject(data)) { data = this.fixBadEPersonRestResponse(data); const object = this.deserialize(data); if (isNotEmpty(data._embedded)) { @@ -43,10 +35,10 @@ export abstract class BaseResponseParsingService { .forEach((property) => { const parsedObj = this.process<ObjectDomain, ObjectType>(data._embedded[property], requestUUID); if (isNotEmpty(parsedObj)) { - if (isPaginatedResponse(data._embedded[property])) { + if (isRestPaginatedList(data._embedded[property])) { object[property] = parsedObj; object[property].page = parsedObj.page.map((obj) => obj.self); - } else if (isObjectLevel(data._embedded[property])) { + } else if (isRestDataObject(data._embedded[property])) { object[property] = parsedObj.self; } else if (Array.isArray(parsedObj)) { object[property] = parsedObj.map((obj) => obj.self) @@ -80,7 +72,7 @@ export abstract class BaseResponseParsingService { list = this.flattenSingleKeyObject(list); } const page: ObjectDomain[] = this.processArray(list, requestUUID); - return new PaginatedList<ObjectDomain>(pageInfo, page); + return new PaginatedList<ObjectDomain>(pageInfo, page, ); } protected processArray<ObjectDomain, ObjectType>(data: any, requestUUID: string): ObjectDomain[] { diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index b936d71c3053822284c91a9f806186b09152eff0..ef294c1296048ccfb8d0224b9cad8a19477a58ee 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedCollection } from '../cache/models/normalized-collection.model'; @@ -10,9 +10,10 @@ import { CommunityDataService } from './community-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { AuthService } from '../auth/auth.service'; -import { Community } from '../shared/community.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from './dso-update-comparator'; @Injectable() export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> { @@ -21,13 +22,15 @@ export class CollectionDataService extends ComColDataService<NormalizedCollectio constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected store: Store<CoreState>, protected cds: CommunityDataService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, - protected http: HttpClient + protected http: HttpClient, + protected comparator: DSOUpdateComparator ) { super(); } diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index 3325dc6ac3fdfbf6adff9d0702876a0dc69b033a..0bf5f857498301f3114708b832d27f88a5a4d29b 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -14,10 +14,12 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestEntry } from './request.reducer'; import { of as observableOf } from 'rxjs'; -import { Community } from '../shared/community.model'; import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from './dso-update-comparator'; +import { UpdateComparator } from './update-comparator'; const LINK_NAME = 'test'; @@ -30,6 +32,7 @@ class TestService extends ComColDataService<NormalizedTestObject, any> { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected store: Store<CoreState>, protected EnvConfig: GlobalConfig, protected cds: CommunityDataService, @@ -38,6 +41,7 @@ class TestService extends ComColDataService<NormalizedTestObject, any> { protected authService: AuthService, protected notificationsService: NotificationsService, protected http: HttpClient, + protected comparator: DSOUpdateComparator, protected linkPath: string ) { super(); @@ -60,6 +64,8 @@ describe('ComColDataService', () => { const EnvConfig = {} as GlobalConfig; const notificationsService = {} as NotificationsService; const http = {} as HttpClient; + const comparator = {} as any; + const dataBuildService = {} as DataBuildService; const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d'; const options = Object.assign(new FindAllOptions(), { @@ -78,7 +84,7 @@ describe('ComColDataService', () => { const authHeader = 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJlaWQiOiJhNjA4NmIzNC0zOTE4LTQ1YjctOGRkZC05MzI5YTcwMmEyNmEiLCJzZyI6W10sImV4cCI6MTUzNDk0MDcyNX0.RV5GAtiX6cpwBN77P_v16iG9ipeyiO7faNYSNMzq_sQ'; const mockHalService = { - getEndpoint: (linkPath) => Observable.of(communitiesEndpoint) + getEndpoint: (linkPath) => observableOf(communitiesEndpoint) }; function initMockCommunityDataService(): CommunityDataService { @@ -112,6 +118,7 @@ describe('ComColDataService', () => { return new TestService( requestService, rdbService, + dataBuildService, store, EnvConfig, cds, @@ -120,6 +127,7 @@ describe('ComColDataService', () => { authService, notificationsService, http, + comparator, LINK_NAME ); } diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index d3eed88ffd43741fc7adf1993bba10ffa3a640ad..0616e9c2edc0f732a321145fd86125ce3c2b66de 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -20,8 +20,9 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestEntry } from './request.reducer'; import { getResponseFromEntry } from '../shared/operators'; +import { CacheableObject } from '../cache/object-cache.reducer'; -export abstract class ComColDataService<TNormalized extends NormalizedObject, TDomain> extends DataService<TNormalized, TDomain> { +export abstract class ComColDataService<TNormalized extends NormalizedObject, TDomain extends CacheableObject> extends DataService<TNormalized, TDomain> { protected abstract cds: CommunityDataService; protected abstract objectCache: ObjectCacheService; protected abstract halService: HALEndpointService; diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 9645a970c67bdb07a8089d6ee1861e2512da5261..40d433a2455708b72461ccf794a90716958995a0 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -18,6 +18,8 @@ import { Observable } from 'rxjs'; import { PaginatedList } from './paginated-list'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from './dso-update-comparator'; @Injectable() export class CommunityDataService extends ComColDataService<NormalizedCommunity, Community> { @@ -28,12 +30,14 @@ export class CommunityDataService extends ComColDataService<NormalizedCommunity, constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected store: Store<CoreState>, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, - protected http: HttpClient + protected http: HttpClient, + protected comparator: DSOUpdateComparator ) { super(); } diff --git a/src/app/core/data/config-response-parsing.service.spec.ts b/src/app/core/data/config-response-parsing.service.spec.ts index a33c5cf5b5c685d5ba5ce03f6ec275e94f1a3595..caf8ef4a192805cbc304e89fcff509c704d24059 100644 --- a/src/app/core/data/config-response-parsing.service.spec.ts +++ b/src/app/core/data/config-response-parsing.service.spec.ts @@ -177,7 +177,7 @@ describe('ConfigResponseParsingService', () => { 'https://rest.api/config/submissionsections/traditionalpagetwo', 'https://rest.api/config/submissionsections/upload', 'https://rest.api/config/submissionsections/license' - ]) + ], 'https://rest.api/config/submissiondefinitions/traditional/sections') }); it('should return a ConfigSuccessResponse if data contains a valid config endpoint response', () => { diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 4bbe775f3b572cc21ce12da296ee019b10065b33..85d65791767f2d9a0c7c5502927dfeb6ad8354bd 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,4 +1,13 @@ -import { delay, distinctUntilChanged, filter, find, switchMap, map, take, tap } from 'rxjs/operators'; +import { + delay, + distinctUntilChanged, + filter, + find, + switchMap, + map, + take, + tap, first, mergeMap +} from 'rxjs/operators'; import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; @@ -32,10 +41,14 @@ import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../cache/respon import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; +import { CacheableObject } from '../cache/object-cache.reducer'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { UpdateComparator } from './update-comparator'; -export abstract class DataService<TNormalized extends NormalizedObject, TDomain> { +export abstract class DataService<TNormalized extends NormalizedObject, TDomain extends CacheableObject> { protected abstract requestService: RequestService; protected abstract rdbService: RemoteDataBuildService; + protected abstract dataBuildService: DataBuildService; protected abstract store: Store<CoreState>; protected abstract linkPath: string; protected abstract halService: HALEndpointService; @@ -43,6 +56,7 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain> protected abstract authService: AuthService; protected abstract notificationsService: NotificationsService; protected abstract http: HttpClient; + protected abstract comparator: UpdateComparator<TNormalized>; public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable<string> @@ -122,15 +136,21 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain> * The patch is derived from the differences between the given object and its version in the object cache * @param {DSpaceObject} object The given object */ - update(object: DSpaceObject) { - const oldVersion = this.objectCache.getBySelfLink(object.self); - const operations = compare(oldVersion, object); - if (isNotEmpty(operations)) { - this.objectCache.addPatch(object.self, operations); - } + update(object: TDomain): Observable<RemoteData<TDomain>> { + const oldVersion$ = this.objectCache.getBySelfLink(object.self); + return oldVersion$.pipe(first(), mergeMap((oldVersion: TNormalized) => { + const newVersion = this.dataBuildService.normalize<TDomain, TNormalized>(object); + const operations = this.comparator.compare(oldVersion, newVersion); + if (isNotEmpty(operations)) { + this.objectCache.addPatch(object.self, operations); + } + return this.findById(object.uuid); + } + )); + } - create(dso: TNormalized, parentUUID: string): Observable<RemoteData<TDomain>> { + create(dso: TDomain, parentUUID: string): Observable<RemoteData<TDomain>> { const requestId = this.requestService.generateRequestId(); const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), @@ -138,7 +158,8 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain> map((endpoint: string) => parentUUID ? `${endpoint}?parent=${parentUUID}` : endpoint) ); - const serializedDso = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(dso.type)).serialize(dso); + const normalizedObject: TNormalized = this.dataBuildService.normalize<TDomain, TNormalized>(dso); + const serializedDso = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(dso.type)).serialize(normalizedObject); const request$ = endpoint$.pipe( take(1), @@ -150,6 +171,7 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain> configureRequest(this.requestService) ).subscribe(); + // Resolve self link for new object const selfLink$ = this.requestService.getByUUID(requestId).pipe( getResponseFromEntry(), map((response: RestResponse) => { diff --git a/src/app/core/data/dso-update-comparator.ts b/src/app/core/data/dso-update-comparator.ts new file mode 100644 index 0000000000000000000000000000000000000000..245fbfaef0273aa3d1302e7a61d7633455d97bf0 --- /dev/null +++ b/src/app/core/data/dso-update-comparator.ts @@ -0,0 +1,12 @@ +import { Operation } from 'fast-json-patch/lib/core'; +import { compare } from 'fast-json-patch'; +import { UpdateComparator } from './update-comparator'; +import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model'; +import { Injectable } from '@angular/core'; + +@Injectable() +export class DSOUpdateComparator implements UpdateComparator<NormalizedDSpaceObject> { + compare(object1: NormalizedDSpaceObject, object2: NormalizedDSpaceObject): Operation[] { + return compare(object1.metadata, object2.metadata).map((operation: Operation) => Object.assign({}, operation, { path: '/metadata' + operation.path })); + } +} diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index 3e8d8bdc04ec423a03928261ecf68cead1e44f57..f1d0f217629b1bf0591b6907996dfe55a94c35f7 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -14,6 +14,8 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from './dso-update-comparator'; /* tslint:disable:max-classes-per-file */ class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject> { @@ -22,12 +24,14 @@ class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject> constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected store: Store<CoreState>, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, - protected http: HttpClient) { + protected http: HttpClient, + protected comparator: DSOUpdateComparator) { super(); } @@ -48,12 +52,14 @@ export class DSpaceObjectDataService { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, - protected http: HttpClient) { - this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, authService, notificationsService, http); + protected http: HttpClient, + protected comparator: DSOUpdateComparator) { + this.dataService = new DataServiceImpl(requestService, rdbService, dataBuildService, null, objectCache, halService, authService, notificationsService, http, comparator); } findById(uuid: string): Observable<RemoteData<DSpaceObject>> { diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 68380ddaa25a60d6bb7e8f9c979e11eef58dd605..411daa9b356e4103375b99b5cec5d94925e03647 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -19,6 +19,8 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { AuthService } from '../auth/auth.service'; import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from './dso-update-comparator'; @Injectable() export class ItemDataService extends DataService<NormalizedItem, Item> { @@ -27,13 +29,15 @@ export class ItemDataService extends DataService<NormalizedItem, Item> { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected store: Store<CoreState>, private bs: BrowseService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, - protected http: HttpClient) { + protected http: HttpClient, + protected comparator: DSOUpdateComparator) { super(); } diff --git a/src/app/core/data/paginated-list.ts b/src/app/core/data/paginated-list.ts index 07d53739d0f9582ed1cfe8739b846d8618a1be1b..8efdccd75d55a77cd15ef531976b87d1ce1dd3f8 100644 --- a/src/app/core/data/paginated-list.ts +++ b/src/app/core/data/paginated-list.ts @@ -81,4 +81,12 @@ export class PaginatedList<T> { set last(last: string) { this.pageInfo.last = last; } + + get self(): string { + return this.pageInfo.self; + } + + set self(self: string) { + this.pageInfo.self = self; + } } diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 285ed065454390879f8ce11441386477d8b677bf..20bc85a87ffbe30241454be3e040f4bf5661ca7f 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -80,7 +80,7 @@ export class RequestService { this.store.pipe(select(this.entryFromUUIDSelector(uuid))), this.store.pipe( select(this.originalUUIDFromUUIDSelector(uuid)), - switchMap((originalUUID) => { + mergeMap((originalUUID) => { return this.store.pipe(select(this.entryFromUUIDSelector(originalUUID))) }, )) diff --git a/src/app/core/data/update-comparator.ts b/src/app/core/data/update-comparator.ts new file mode 100644 index 0000000000000000000000000000000000000000..884ac585f54647811abb9dcec71ad37c575535d5 --- /dev/null +++ b/src/app/core/data/update-comparator.ts @@ -0,0 +1,6 @@ +import { NormalizedObject } from '../cache/models/normalized-object.model'; +import { Operation } from 'fast-json-patch/lib/core'; + +export interface UpdateComparator<TNormalized extends NormalizedObject> { + compare(object1: TNormalized, object2: TNormalized): Operation[]; +} \ No newline at end of file diff --git a/src/app/core/shared/dspace-object.model.ts b/src/app/core/shared/dspace-object.model.ts index 68338143ba0cad5e0b5e800a585c12a1fe67cfef..3e08da151cead2f5f8ab26ea3c56f84820dec2df 100644 --- a/src/app/core/shared/dspace-object.model.ts +++ b/src/app/core/shared/dspace-object.model.ts @@ -34,14 +34,15 @@ export class DSpaceObject implements CacheableObject, ListableObject { /** * The name for this DSpaceObject */ - @autoserialize - name: string; + get name(): string { + return this.findMetadata('dc.title'); + } /** * An array containing all metadata of this DSpaceObject */ @autoserialize - metadata: Metadatum[]; + metadata: Metadatum[] = []; /** * An array of DSpaceObjects that are direct parents of this DSpaceObject diff --git a/src/app/core/shared/page-info.model.ts b/src/app/core/shared/page-info.model.ts index ba2af24dce33e16b746ce40598abe5788f1a837e..4ed281657d2b3ed9c0b7608799215e4e06760d9b 100644 --- a/src/app/core/shared/page-info.model.ts +++ b/src/app/core/shared/page-info.model.ts @@ -39,4 +39,7 @@ export class PageInfo { @autoserialize first: string; + + @autoserialize + self: string; } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts index 3544bce280081f36a37efa576d3712a6eace55ea..8a3cf52abbce14c68aff9372af86b52e08605481 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts @@ -96,7 +96,6 @@ export class DsDynamicFormControlComponent extends DynamicFormControlContainerCo } static getFormControlType(model: DynamicFormControlModel): Type<DynamicFormControl> | null { - switch (model.type) { case DYNAMIC_FORM_CONTROL_TYPE_ARRAY: diff --git a/src/app/shared/form/form.component.html b/src/app/shared/form/form.component.html index 958c9a6c73cd4edd4e7c30582f13d55004a59daa..18d6533df4fddfed983fa97283524c1118facd12 100644 --- a/src/app/shared/form/form.component.html +++ b/src/app/shared/form/form.component.html @@ -51,7 +51,7 @@ <div class="col text-right"> <button type="reset" class="btn btn-default" (click)="reset()">{{'form.cancel' | translate}}</button> - <button type="submit" class="btn btn-primary" (click)="onSubmit($event)" + <button type="submit" class="btn btn-primary" (click)="onSubmit()" [disabled]="!(isValid() | async)">{{'form.submit' | translate}} </button> </div> diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index 2d74ddf8d4e75c07ce2d76f053b4232d07d2c0e6..9848f9feedc437c49bd46931061fb639c8eb8473 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -73,7 +73,7 @@ export class FormComponent implements OnDestroy, OnInit { * An event fired when form is valid and submitted . * Event's payload equals to the form content. */ - @Output() submit: EventEmitter<Observable<any>> = new EventEmitter<Observable<any>>(); + @Output() submitForm: EventEmitter<Observable<any>> = new EventEmitter<Observable<any>>(); /** * An object of FormGroup type @@ -264,7 +264,7 @@ export class FormComponent implements OnDestroy, OnInit { */ onSubmit(): void { if (this.getFormGroupValidStatus()) { - this.submit.emit(this.formService.getFormData(this.formId)); + this.submitForm.emit(this.formService.getFormData(this.formId)); } else { this.formService.validateAllFormFields(this.formGroup); } diff --git a/src/app/shared/form/form.service.ts b/src/app/shared/form/form.service.ts index ae5ba9f278ca079803469e7512de031ecb4a6cf4..9356f86e8cf5faadc1c0c69cf0260f670a6ef277 100644 --- a/src/app/shared/form/form.service.ts +++ b/src/app/shared/form/form.service.ts @@ -98,7 +98,7 @@ export class FormService { const errorKey = this.getValidatorNameFromMap(message); let errorMsg = message; - // if form control model has not errorMessages object, create it + // if form control model has no errorMessages object, create it if (!model.errorMessages) { model.errorMessages = {}; }