diff --git a/src/app/+login-page/login-page.component.html b/src/app/+login-page/login-page.component.html index 6dcb11fbb0..84059877f4 100644 --- a/src/app/+login-page/login-page.component.html +++ b/src/app/+login-page/login-page.component.html @@ -3,7 +3,8 @@

{{"login.form.header" | translate}}

- +
diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index 816633ec38..6ca449460b 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -81,7 +81,6 @@ const components = [ SearchFilterService, SearchFixedFilterService, ConfigurationSearchPageGuard, - SearchFilterService, SearchConfigurationService ], entryComponents: [ diff --git a/src/app/core/auth/auth.service.spec.ts b/src/app/core/auth/auth.service.spec.ts index ab2e6fd86b..5084dc8596 100644 --- a/src/app/core/auth/auth.service.spec.ts +++ b/src/app/core/auth/auth.service.spec.ts @@ -23,11 +23,14 @@ import { AppState } from '../../app.reducer'; import { ClientCookieService } from '../services/client-cookie.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service'; +import { routeServiceStub } from '../../shared/testing/route-service-stub'; +import { RouteService } from '../services/route.service'; describe('AuthService test', () => { let mockStore: Store; let authService: AuthService; + let routeServiceMock: RouteService; let authRequest; let window; let routerStub; @@ -74,6 +77,7 @@ describe('AuthService test', () => { { provide: NativeWindowService, useValue: window }, { provide: REQUEST, useValue: {} }, { provide: Router, useValue: routerStub }, + { provide: RouteService, useValue: routeServiceStub }, { provide: ActivatedRoute, useValue: routeStub }, { provide: Store, useValue: mockStore }, { provide: RemoteDataBuildService, useValue: rdbService }, @@ -138,6 +142,7 @@ describe('AuthService test', () => { { provide: AuthRequestService, useValue: authRequest }, { provide: REQUEST, useValue: {} }, { provide: Router, useValue: routerStub }, + { provide: RouteService, useValue: routeServiceStub }, { provide: RemoteDataBuildService, useValue: rdbService }, CookieService, AuthService @@ -145,13 +150,13 @@ describe('AuthService test', () => { }).compileComponents(); })); - beforeEach(inject([CookieService, AuthRequestService, Store, Router], (cookieService: CookieService, authReqService: AuthRequestService, store: Store, router: Router) => { + beforeEach(inject([CookieService, AuthRequestService, Store, Router, RouteService], (cookieService: CookieService, authReqService: AuthRequestService, store: Store, router: Router, routeService: RouteService) => { store .subscribe((state) => { (state as any).core = Object.create({}); (state as any).core.auth = authenticatedState; }); - authService = new AuthService({}, window, undefined, authReqService, router, cookieService, store, rdbService); + authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService); })); it('should return true when user is logged in', () => { @@ -189,6 +194,7 @@ describe('AuthService test', () => { { provide: AuthRequestService, useValue: authRequest }, { provide: REQUEST, useValue: {} }, { provide: Router, useValue: routerStub }, + { provide: RouteService, useValue: routeServiceStub }, { provide: RemoteDataBuildService, useValue: rdbService }, ClientCookieService, CookieService, @@ -197,7 +203,7 @@ describe('AuthService test', () => { }).compileComponents(); })); - beforeEach(inject([ClientCookieService, AuthRequestService, Store, Router], (cookieService: ClientCookieService, authReqService: AuthRequestService, store: Store, router: Router) => { + beforeEach(inject([ClientCookieService, AuthRequestService, Store, Router, RouteService], (cookieService: ClientCookieService, authReqService: AuthRequestService, store: Store, router: Router, routeService: RouteService) => { const expiredToken: AuthTokenInfo = new AuthTokenInfo('test_token'); expiredToken.expires = Date.now() - (1000 * 60 * 60); authenticatedState = { @@ -212,11 +218,14 @@ describe('AuthService test', () => { (state as any).core = Object.create({}); (state as any).core.auth = authenticatedState; }); - authService = new AuthService({}, window, undefined, authReqService, router, cookieService, store, rdbService); + authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService); storage = (authService as any).storage; + routeServiceMock = TestBed.get(RouteService); + routerStub = TestBed.get(Router); spyOn(storage, 'get'); spyOn(storage, 'remove'); spyOn(storage, 'set'); + })); it('should throw false when token is not valid', () => { @@ -238,5 +247,32 @@ describe('AuthService test', () => { expect(storage.remove).toHaveBeenCalled(); }); + it ('should set redirect url to previous page', () => { + spyOn(routeServiceMock, 'getHistory').and.callThrough(); + authService.redirectAfterLoginSuccess(true); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(routerStub.navigate).toHaveBeenCalledWith(['/collection/123']); + }); + + it ('should set redirect url to current page', () => { + spyOn(routeServiceMock, 'getHistory').and.callThrough(); + authService.redirectAfterLoginSuccess(false); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(routerStub.navigate).toHaveBeenCalledWith(['/home']); + }); + + it ('should redirect to / and not to /login', () => { + spyOn(routeServiceMock, 'getHistory').and.returnValue(observableOf(['/login', '/login'])); + authService.redirectAfterLoginSuccess(true); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(routerStub.navigate).toHaveBeenCalledWith(['/']); + }); + + it ('should redirect to / when no redirect url is found', () => { + spyOn(routeServiceMock, 'getHistory').and.returnValue(observableOf([''])); + authService.redirectAfterLoginSuccess(true); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(routerStub.navigate).toHaveBeenCalledWith(['/']); + }); }); }); diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 08c94b02f2..5287e537ee 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -22,6 +22,7 @@ import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth. import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import {RouteService} from '../services/route.service'; export const LOGIN_ROUTE = '/login'; export const LOGOUT_ROUTE = '/logout'; @@ -45,6 +46,7 @@ export class AuthService { protected authRequestService: AuthRequestService, @Optional() @Inject(RESPONSE) private response: any, protected router: Router, + protected routeService: RouteService, protected storage: CookieService, protected store: Store, protected rdbService: RemoteDataBuildService @@ -337,7 +339,7 @@ export class AuthService { /** * Redirect to the route navigated before the login */ - public redirectToPreviousUrl() { + public redirectAfterLoginSuccess(isStandalonePage: boolean) { this.getRedirectUrl().pipe( take(1)) .subscribe((redirectUrl) => { @@ -346,18 +348,39 @@ export class AuthService { this.clearRedirectUrl(); this.router.onSameUrlNavigation = 'reload'; const url = decodeURIComponent(redirectUrl); - this.router.navigateByUrl(url); - /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */ - // this._window.nativeWindow.location.href = url; + this.navigateToRedirectUrl(url); } else { - this.router.navigate(['/']); - /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */ - // this._window.nativeWindow.location.href = '/'; + // If redirectUrl is empty use history. + this.routeService.getHistory().pipe( + take(1) + ).subscribe((history) => { + let redirUrl; + if (isStandalonePage) { + // For standalone login pages, use the previous route. + redirUrl = history[history.length - 2] || ''; + } else { + redirUrl = history[history.length - 1] || ''; + } + this.navigateToRedirectUrl(redirUrl); + }); } - }) + }); } + protected navigateToRedirectUrl(url: string) { + // in case the user navigates directly to /login (via bookmark, etc), or the route history is not found. + if (isEmpty(url) || url.startsWith(LOGIN_ROUTE)) { + this.router.navigate(['/']); + /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */ + // this._window.nativeWindow.location.href = '/'; + } else { + /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */ + // this._window.nativeWindow.location.href = url; + this.router.navigate([url]); + } + } + /** * Refresh route navigated */ @@ -400,4 +423,5 @@ export class AuthService { this.store.dispatch(new SetRedirectUrlAction('')); this.storage.remove(REDIRECT_COOKIE); } + } diff --git a/src/app/core/auth/server-auth.service.ts b/src/app/core/auth/server-auth.service.ts index c344683e38..cf4d4a658e 100644 --- a/src/app/core/auth/server-auth.service.ts +++ b/src/app/core/auth/server-auth.service.ts @@ -1,12 +1,12 @@ -import { map, switchMap, take } from 'rxjs/operators'; +import { filter, map, switchMap, take } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { HttpHeaders } from '@angular/common/http'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { AuthStatus } from './models/auth-status.model'; -import { isNotEmpty } from '../../shared/empty.util'; -import { AuthService } from './auth.service'; +import { isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { AuthService, LOGIN_ROUTE } from './auth.service'; import { AuthTokenInfo } from './models/auth-token-info.model'; import { CheckAuthenticationTokenAction } from './auth.actions'; import { EPerson } from '../eperson/models/eperson.model'; @@ -54,7 +54,7 @@ export class ServerAuthService extends AuthService { /** * Redirect to the route navigated before the login */ - public redirectToPreviousUrl() { + public redirectAfterLoginSuccess(isStandalonePage: boolean) { this.getRedirectUrl().pipe( take(1)) .subscribe((redirectUrl) => { @@ -67,10 +67,15 @@ export class ServerAuthService extends AuthService { const url = decodeURIComponent(redirectUrl); this.router.navigateByUrl(url); } else { - this.router.navigate(['/']); + // If redirectUrl is empty use history. For ssr the history array should contain the requested url. + this.routeService.getHistory().pipe( + filter((history) => history.length > 0), + take(1) + ).subscribe((history) => { + this.navigateToRedirectUrl(history[history.length - 1] || ''); + }); } }) - } } diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 38b86b3817..cd59e7ac65 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -26,6 +26,7 @@ import { GenericConstructor } from '../shared/generic-constructor'; import { hasValue, isNotEmptyOperator } from '../../shared/empty.util'; import { DSpaceObject } from '../shared/dspace-object.model'; import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; +import { SearchParam } from '../cache/models/search-param.model'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -47,6 +48,36 @@ export class CollectionDataService extends ComColDataService { super(); } + /** + * Get all collections the user is authorized to submit to + * + * @param options The [[FindAllOptions]] object + * @return Observable>> + * collection list + */ + getAuthorizedCollection(options: FindAllOptions = {}): Observable>> { + const searchHref = 'findAuthorized'; + + return this.searchBy(searchHref, options).pipe( + filter((collections: RemoteData>) => !collections.isResponsePending)); + } + + /** + * Get all collections the user is authorized to submit to, by community + * + * @param communityId The community id + * @param options The [[FindAllOptions]] object + * @return Observable>> + * collection list + */ + getAuthorizedCollectionByCommunity(communityId: string, options: FindAllOptions = {}): Observable>> { + const searchHref = 'findAuthorizedByCommunity'; + options.searchParams = [new SearchParam('uuid', communityId)]; + + return this.searchBy(searchHref, options).pipe( + filter((collections: RemoteData>) => !collections.isResponsePending)); + } + /** * Find whether there is a collection whom user has authorization to submit to * diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 775118dbc0..0980d48537 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -3,7 +3,7 @@ import { HttpHeaders } from '@angular/common/http'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { Observable, race as observableRace } from 'rxjs'; -import { filter, find, map, mergeMap, take } from 'rxjs/operators'; +import { filter, map, mergeMap, take } from 'rxjs/operators'; import { cloneDeep, remove } from 'lodash'; import { AppState } from '../../app.reducer'; @@ -262,12 +262,13 @@ export class RequestService { */ private clearRequestsOnTheirWayToTheStore(request: GetRequest) { this.getByHref(request.href).pipe( - find((re: RequestEntry) => hasValue(re))) - .subscribe((re: RequestEntry) => { - if (!re.responsePending) { - remove(this.requestsOnTheirWayToTheStore, (item) => item === request.href); - } - }); + filter((re: RequestEntry) => hasValue(re)), + take(1) + ).subscribe((re: RequestEntry) => { + if (!re.responsePending) { + remove(this.requestsOnTheirWayToTheStore, (item) => item === request.href); + } + }); } /** diff --git a/src/app/core/submission/submission-response-parsing.service.ts b/src/app/core/submission/submission-response-parsing.service.ts index a0811c8f2d..de7d683d91 100644 --- a/src/app/core/submission/submission-response-parsing.service.ts +++ b/src/app/core/submission/submission-response-parsing.service.ts @@ -128,7 +128,10 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService // Iterate over all workspaceitem's sections Object.keys(item.sections) .forEach((sectionId) => { - if (typeof item.sections[sectionId] === 'object' && isNotEmpty(item.sections[sectionId])) { + if (typeof item.sections[sectionId] === 'object' && (isNotEmpty(item.sections[sectionId]) && + // When Upload section is disabled, add to submission only if there are files + (!item.sections[sectionId].hasOwnProperty('files') || isNotEmpty((item.sections[sectionId] as any).files)))) { + const normalizedSectionData = Object.create({}); // Iterate over all sections property Object.keys(item.sections[sectionId]) diff --git a/src/app/shared/auth-nav-menu/auth-nav-menu.component.html b/src/app/shared/auth-nav-menu/auth-nav-menu.component.html index b560283ad5..4df07880d8 100644 --- a/src/app/shared/auth-nav-menu/auth-nav-menu.component.html +++ b/src/app/shared/auth-nav-menu/auth-nav-menu.component.html @@ -3,7 +3,8 @@ diff --git a/src/app/shared/chips/models/chips-item.model.ts b/src/app/shared/chips/models/chips-item.model.ts index 540f94166f..913232fa71 100644 --- a/src/app/shared/chips/models/chips-item.model.ts +++ b/src/app/shared/chips/models/chips-item.model.ts @@ -2,6 +2,7 @@ import { isObject, uniqueId } from 'lodash'; import { hasValue, isNotEmpty } from '../../empty.util'; import { FormFieldMetadataValueObject } from '../../form/builder/models/form-field-metadata-value.model'; import { ConfidenceType } from '../../../core/integration/models/confidence-type'; +import { PLACEHOLDER_PARENT_METADATA } from '../../form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model'; export interface ChipsItemIcon { metadata: string; @@ -62,7 +63,7 @@ export class ChipsItem { if (this._item.hasOwnProperty(icon.metadata) && (((typeof this._item[icon.metadata] === 'string') && hasValue(this._item[icon.metadata])) || (this._item[icon.metadata] as FormFieldMetadataValueObject).hasValue()) - && !(this._item[icon.metadata] as FormFieldMetadataValueObject).hasPlaceholder()) { + && !this.hasPlaceholder(this._item[icon.metadata])) { if ((icon.visibleWhenAuthorityEmpty || (this._item[icon.metadata] as FormFieldMetadataValueObject).confidence !== ConfidenceType.CF_UNSET) && isNotEmpty(icon.style)) { @@ -109,4 +110,9 @@ export class ChipsItem { this.display = value; } + + private hasPlaceholder(value: any) { + return (typeof value === 'string') ? (value === PLACEHOLDER_PARENT_METADATA) : + (value as FormFieldMetadataValueObject).hasPlaceholder() + } } 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 217f9e79cf..52a924604f 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 @@ -14,7 +14,8 @@ - +
{{ message | translate:model.validators }} diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts index fc618023f9..66bdf97dad 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts @@ -1,4 +1,7 @@ import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicFormGroupModelConfig, serializable } from '@ng-dynamic-forms/core'; + +import { Subject } from 'rxjs'; + import { isNotEmpty } from '../../../../empty.util'; import { DsDynamicInputModel } from './ds-dynamic-input.model'; import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model'; @@ -16,12 +19,16 @@ export class DynamicConcatModel extends DynamicFormGroupModel { @serializable() separator: string; @serializable() hasLanguages = false; isCustomGroup = true; + valueUpdates: Subject; constructor(config: DynamicConcatModelConfig, layout?: DynamicFormControlLayout) { super(config, layout); this.separator = config.separator + ' '; + + this.valueUpdates = new Subject(); + this.valueUpdates.subscribe((value: string) => this.value = value); } get value() { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts index 860c481820..4e4a944319 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts @@ -28,6 +28,7 @@ export class DsDynamicInputModel extends DynamicInputModel { constructor(config: DsDynamicInputModelConfig, layout?: DynamicFormControlLayout) { super(config, layout); + this.hint = config.hint; this.readOnly = config.readOnly; this.value = config.value; this.language = config.language; @@ -57,11 +58,7 @@ export class DsDynamicInputModel extends DynamicInputModel { } get hasLanguages(): boolean { - if (this.languageCodes && this.languageCodes.length > 1) { - return true; - } else { - return false; - } + return this.languageCodes && this.languageCodes.length > 1; } get language(): string { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts index 6bd5a604a0..5d2cbc58b7 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts @@ -1,5 +1,5 @@ -import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicInputModelConfig, serializable } from '@ng-dynamic-forms/core'; -import { DsDynamicInputModel, DsDynamicInputModelConfig } from './ds-dynamic-input.model'; +import { DynamicFormControlLayout, DynamicFormGroupModel, serializable } from '@ng-dynamic-forms/core'; +import { DsDynamicInputModel } from './ds-dynamic-input.model'; import { Subject } from 'rxjs'; import { DynamicFormGroupModelConfig } from '@ng-dynamic-forms/core/src/model/form-group/dynamic-form-group.model'; import { LanguageCode } from '../../models/form-field-language-value.model'; @@ -12,6 +12,7 @@ export interface DsDynamicQualdropModelConfig extends DynamicFormGroupModelConfi languageCodes?: LanguageCode[]; language?: string; readOnly: boolean; + hint?: string; } export class DynamicQualdropModel extends DynamicFormGroupModel { @@ -20,6 +21,7 @@ export class DynamicQualdropModel extends DynamicFormGroupModel { @serializable() languageUpdates: Subject; @serializable() hasLanguages = false; @serializable() readOnly: boolean; + @serializable() hint: string; isCustomGroup = true; constructor(config: DsDynamicQualdropModelConfig, layout?: DynamicFormControlLayout) { @@ -33,6 +35,8 @@ export class DynamicQualdropModel extends DynamicFormGroupModel { this.languageUpdates.subscribe((lang: string) => { this.language = lang; }); + + this.hint = config.hint; } get value() { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html index cb2d1fe217..3cfb5980c6 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html @@ -20,11 +20,10 @@ [disabled]="isInputDisabled()" [placeholder]="model.placeholder | translate" [readonly]="model.readOnly" - (change)="$event.preventDefault()" + (change)="onChange($event)" (blur)="onBlurEvent($event); $event.stopPropagation(); sdRef.close();" (focus)="onFocusEvent($event); $event.stopPropagation(); sdRef.close();" - (click)="$event.stopPropagation(); $event.stopPropagation(); sdRef.close();" - (input)="onInput($event)"> + (click)="$event.stopPropagation(); $event.stopPropagation(); sdRef.close();">
@@ -40,11 +39,10 @@ [disabled]="firstInputValue.length === 0 || isInputDisabled()" [placeholder]="model.secondPlaceholder | translate" [readonly]="model.readOnly" - (change)="$event.preventDefault()" + (change)="onChange($event)" (blur)="onBlurEvent($event); $event.stopPropagation(); sdRef.close();" (focus)="onFocusEvent($event); $event.stopPropagation(); sdRef.close();" - (click)="$event.stopPropagation(); sdRef.close();" - (input)="onInput($event)"> + (click)="$event.stopPropagation(); sdRef.close();">
{{('mydspace.results.no-files' | translate)}} diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.spec.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.spec.ts index 7920e54b00..7c9e861c7e 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.spec.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.spec.ts @@ -12,10 +12,20 @@ import { MockTranslateLoader } from '../../../mocks/mock-translate-loader'; import { ItemDetailPreviewFieldComponent } from './item-detail-preview-field/item-detail-preview-field.component'; import { FileSizePipe } from '../../../utils/file-size-pipe'; import { VarDirective } from '../../../utils/var.directive'; +import { FileService } from '../../../../core/shared/file.service'; +import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; +import { HALEndpointServiceStub } from '../../../testing/hal-endpoint-service-stub'; import { RemoteData } from '../../../../core/data/remote-data'; import { PaginatedList } from '../../../../core/data/paginated-list'; import { PageInfo } from '../../../../core/shared/page-info.model'; +function getMockFileService(): FileService { + return jasmine.createSpyObj('FileService', { + downloadFile: jasmine.createSpy('downloadFile'), + getFileNameFromResponseContentDisposition: jasmine.createSpy('getFileNameFromResponseContentDisposition') + }); +} + let component: ItemDetailPreviewComponent; let fixture: ComponentFixture; @@ -62,6 +72,10 @@ describe('ItemDetailPreviewComponent', () => { }), ], declarations: [ItemDetailPreviewComponent, ItemDetailPreviewFieldComponent, TruncatePipe, FileSizePipe, VarDirective], + providers: [ + { provide: FileService, useValue: getMockFileService() }, + { provide: HALEndpointService, useValue: new HALEndpointServiceStub('workspaceitems') } + ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ItemDetailPreviewComponent, { set: { changeDetection: ChangeDetectionStrategy.Default } diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts index d26bfc4589..fa15c71168 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts @@ -1,12 +1,15 @@ import { Component, Input } from '@angular/core'; import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; import { Item } from '../../../../core/shared/item.model'; import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { fadeInOut } from '../../../animations/fade'; import { Bitstream } from '../../../../core/shared/bitstream.model'; import { MyDSpaceResult } from '../../../../+my-dspace-page/my-dspace-result.model'; +import { FileService } from '../../../../core/shared/file.service'; +import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; /** * This component show metadata for the given item object in the detail view. @@ -54,6 +57,16 @@ export class ItemDetailPreviewComponent { */ public thumbnail$: Observable; + /** + * Initialize instance variables + * + * @param {FileService} fileService + * @param {HALEndpointService} halService + */ + constructor(private fileService: FileService, + private halService: HALEndpointService) { + } + /** * Initialize all instance variables */ @@ -62,4 +75,15 @@ export class ItemDetailPreviewComponent { this.bitstreams$ = this.item.getFiles(); } + /** + * Perform bitstream download + */ + public downloadBitstreamFile(uuid: string) { + this.halService.getEndpoint('bitstreams').pipe( + first()) + .subscribe((url) => { + const fileUrl = `${url}/${uuid}/content`; + this.fileService.downloadFile(fileUrl); + }); + } } diff --git a/src/app/shared/testing/auth-service-stub.ts b/src/app/shared/testing/auth-service-stub.ts index a65923dcab..a6d24d5c8b 100644 --- a/src/app/shared/testing/auth-service-stub.ts +++ b/src/app/shared/testing/auth-service-stub.ts @@ -10,6 +10,7 @@ export class AuthServiceStub { token: AuthTokenInfo = new AuthTokenInfo('token_test'); private _tokenExpired = false; + private redirectUrl; constructor() { this.token.expires = Date.now() + (1000 * 60 * 60); @@ -88,7 +89,11 @@ export class AuthServiceStub { } setRedirectUrl(url: string) { - return; + this.redirectUrl = url; + } + + getRedirectUrl() { + return observableOf(this.redirectUrl); } public storeToken(token: AuthTokenInfo) { diff --git a/src/app/shared/testing/route-service-stub.ts b/src/app/shared/testing/route-service-stub.ts index e3cdd9d8c6..a493f10a13 100644 --- a/src/app/shared/testing/route-service-stub.ts +++ b/src/app/shared/testing/route-service-stub.ts @@ -27,6 +27,9 @@ export const routeServiceStub: any = { }, getRouteDataValue: (param) => { return observableOf({}) + }, + getHistory: () => { + return observableOf(['/home','/collection/123','/home']) } /* tslint:enable:no-empty */ }; diff --git a/src/app/submission/form/collection/submission-form-collection.component.html b/src/app/submission/form/collection/submission-form-collection.component.html index 37ada35155..6f4a8a864c 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.html +++ b/src/app/submission/form/collection/submission-form-collection.component.html @@ -10,10 +10,10 @@ class="btn btn-outline-primary" (blur)="onClose()" (click)="onClose()" - [disabled]="(disabled$ | async)" + [disabled]="(disabled$ | async) || (processingChange$ | async)" ngbDropdownToggle> - - {{ selectedCollectionName$ | async }} + + {{ selectedCollectionName$ | async }}
Object.assign(new SearchResult(), { indexableObject: collection })); -const searchService = { - search: () => { - return observableOf(new RemoteData(true, true, true, - undefined, new PaginatedList(new PageInfo(), collectionResults))) - } -}; +const mockCommunity = Object.assign(new Community(), { + name: 'Community 1', + id: '123456789-1', + metadata: [ + { + key: 'dc.title', + language: 'en_US', + value: 'Community 1' + }], + collections: observableOf(new RemoteData(true, true, true, + undefined, new PaginatedList(new PageInfo(), [mockCommunity1Collection1, mockCommunity1Collection2]))), + subcommunities: observableOf(new RemoteData(true, true, true, + undefined, new PaginatedList(new PageInfo(), subcommunities))), +}); + +const mockCommunity2 = Object.assign(new Community(), { + name: 'Community 2', + id: '123456789-2', + metadata: [ + { + key: 'dc.title', + language: 'en_US', + value: 'Community 2' + }], + collections: observableOf(new RemoteData(true, true, true, + undefined, new PaginatedList(new PageInfo(), [mockCommunity2Collection1, mockCommunity2Collection2]))), + subcommunities: observableOf(new RemoteData(true, true, true, + undefined, new PaginatedList(new PageInfo(), []))), +}); + +const mockCommunity1Collection1Rd = observableOf(new RemoteData(true, true, true, + undefined, mockCommunity1Collection1)); + +const mockCommunityList = observableOf(new RemoteData(true, true, true, + undefined, new PaginatedList(new PageInfo(), [mockCommunity, mockCommunity2]))); + +const mockCommunityCollectionList = observableOf(new RemoteData(true, true, true, + undefined, new PaginatedList(new PageInfo(), [mockCommunity1Collection1, mockCommunity1Collection2]))); + +const mockCommunity2CollectionList = observableOf(new RemoteData(true, true, true, + undefined, new PaginatedList(new PageInfo(), [mockCommunity2Collection1, mockCommunity2Collection2]))); const mockCollectionList = [ { communities: [ { - id: 'c0e4de93-f506-4990-a840-d406f6f2ada7', - name: 'Submission test' + id: '123456789-1', + name: 'Community 1' } ], collection: { @@ -106,8 +151,8 @@ const mockCollectionList = [ { communities: [ { - id: 'c0e4de93-f506-4990-a840-d406f6f2ada7', - name: 'Submission test' + id: '123456789-1', + name: 'Community 1' } ], collection: { @@ -118,8 +163,8 @@ const mockCollectionList = [ { communities: [ { - id: 'c0e4de93-f506-4990-a840-d406f6f2ada7', - name: 'Submission test' + id: '123456789-2', + name: 'Community 2' } ], collection: { @@ -130,8 +175,8 @@ const mockCollectionList = [ { communities: [ { - id: 'c0e4de93-f506-4990-a840-d406f6f2ada7', - name: 'Submission test' + id: '123456789-2', + name: 'Community 2' } ], collection: { @@ -158,6 +203,12 @@ describe('SubmissionFormCollectionComponent Component', () => { const communityDataService: any = jasmine.createSpyObj('communityDataService', { findAll: jasmine.createSpy('findAll') }); + + const collectionDataService: any = jasmine.createSpyObj('collectionDataService', { + findById: jasmine.createSpy('findById'), + getAuthorizedCollectionByCommunity: jasmine.createSpy('getAuthorizedCollectionByCommunity') + }); + const store: any = jasmine.createSpyObj('store', { dispatch: jasmine.createSpy('dispatch'), select: jasmine.createSpy('select') @@ -179,15 +230,12 @@ describe('SubmissionFormCollectionComponent Component', () => { TestComponent ], providers: [ - { - provide: SubmissionJsonPatchOperationsService, - useClass: SubmissionJsonPatchOperationsServiceStub - }, + { provide: CollectionDataService, useValue: collectionDataService }, + { provide: SubmissionJsonPatchOperationsService, useClass: SubmissionJsonPatchOperationsServiceStub }, { provide: SubmissionService, useClass: SubmissionServiceStub }, { provide: CommunityDataService, useValue: communityDataService }, { provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder }, { provide: Store, useValue: store }, - { provide: SearchService, useValue: searchService }, ChangeDetectorRef, SubmissionFormCollectionComponent ], @@ -252,17 +300,21 @@ describe('SubmissionFormCollectionComponent Component', () => { }); it('should init collection list properly', () => { + communityDataService.findAll.and.returnValue(mockCommunityList); + collectionDataService.findById.and.returnValue(mockCommunity1Collection1Rd); + collectionDataService.getAuthorizedCollectionByCommunity.and.returnValues(mockCommunityCollectionList, mockCommunity2CollectionList); + comp.ngOnChanges({ currentCollectionId: new SimpleChange(null, collectionId, true) }); - expect(comp.searchListCollection$).toBeObservable(cold('(b)', { + expect(comp.searchListCollection$).toBeObservable(cold('(ab)', { + a: [], b: mockCollectionList })); - expect(comp.selectedCollectionName$).toBeObservable(cold('(ab|)', { - a: '', - b: 'Community 1-Collection 1' + expect(comp.selectedCollectionName$).toBeObservable(cold('(a|)', { + a: 'Community 1-Collection 1' })); }); @@ -394,8 +446,6 @@ class TestComponent { definitionId = 'traditional'; submissionId = mockSubmissionId; - onCollectionChange = () => { - return; - } + onCollectionChange = () => { return; } } diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index e9832985fe..79d2f2a7bc 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -17,13 +17,16 @@ import { distinctUntilChanged, filter, find, + flatMap, map, mergeMap, + reduce, startWith } from 'rxjs/operators'; import { Collection } from '../../../core/shared/collection.model'; import { CommunityDataService } from '../../../core/data/community-data.service'; +import { Community } from '../../../core/shared/community.model'; import { hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util'; import { RemoteData } from '../../../core/data/remote-data'; import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; @@ -32,12 +35,8 @@ import { PaginatedList } from '../../../core/data/paginated-list'; import { SubmissionService } from '../../submission.service'; import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; -import { SearchService } from '../../../+search-page/search-service/search.service'; -import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model'; -import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; -import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; -import { getSucceededRemoteData } from '../../../core/shared/operators'; -import { SearchResult } from '../../../+search-page/search-result.model'; +import { CollectionDataService } from '../../../core/data/collection-data.service'; +import { FindAllOptions } from '../../../core/data/request.models'; /** * An interface to represent a collection entry @@ -95,6 +94,12 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { */ public disabled$ = new BehaviorSubject(true); + /** + * A boolean representing if a collection change operation is processing + * @type {BehaviorSubject} + */ + public processingChange$ = new BehaviorSubject(false); + /** * The search form control * @type {FormControl} @@ -148,17 +153,17 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { * * @param {ChangeDetectorRef} cdr * @param {CommunityDataService} communityDataService + * @param {CollectionDataService} collectionDataService * @param {JsonPatchOperationsBuilder} operationsBuilder * @param {SubmissionJsonPatchOperationsService} operationsService * @param {SubmissionService} submissionService - * @param {SearchService} searchService */ constructor(protected cdr: ChangeDetectorRef, private communityDataService: CommunityDataService, + private collectionDataService: CollectionDataService, private operationsBuilder: JsonPatchOperationsBuilder, private operationsService: SubmissionJsonPatchOperationsService, - private submissionService: SubmissionService, - private searchService: SearchService) { + private submissionService: SubmissionService) { } /** @@ -195,57 +200,55 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { && hasValue(changes.currentCollectionId.currentValue)) { this.selectedCollectionId = this.currentCollectionId; - // // @TODO replace with search/top browse endpoint - // // @TODO implement community/subcommunity hierarchy - // const communities$ = this.communityDataService.findAll().pipe( - // find((communities: RemoteData>) => isNotEmpty(communities.payload)), - // mergeMap((communities: RemoteData>) => communities.payload.page)); + this.selectedCollectionName$ = this.collectionDataService.findById(this.currentCollectionId).pipe( + find((collectionRD: RemoteData) => isNotEmpty(collectionRD.payload)), + map((collectionRD: RemoteData) => collectionRD.payload.name) + ); - const listCollection$: Observable = this.searchService.search( - new PaginatedSearchOptions({ - dsoType: DSpaceObjectType.COLLECTION, - pagination: new PaginationComponentOptions(), - scope: 'c0e4de93-f506-4990-a840-d406f6f2ada7' - }) - ).pipe( - getSucceededRemoteData(), - map((collections: RemoteData>>) => collections.payload.page), - filter((collectionData: Array>) => isNotEmpty(collectionData)), - map((collectionData: Array>) => { - return collectionData.map((collection: SearchResult) => { - return { - communities: [{ - id: 'c0e4de93-f506-4990-a840-d406f6f2ada7', - name: 'Submission test' - }], - collection: { id: collection.indexableObject.id, name: collection.indexableObject.name } + const findOptions: FindAllOptions = { + elementsPerPage: 1000 + }; + + // Retrieve collection list only when is the first change + if (changes.currentCollectionId.isFirstChange()) { + // @TODO replace with search/top browse endpoint + // @TODO implement community/subcommunity hierarchy + const communities$ = this.communityDataService.findAll(findOptions).pipe( + find((communities: RemoteData>) => isNotEmpty(communities.payload)), + mergeMap((communities: RemoteData>) => communities.payload.page)); + + const listCollection$ = communities$.pipe( + flatMap((communityData: Community) => { + return this.collectionDataService.getAuthorizedCollectionByCommunity(communityData.uuid, findOptions).pipe( + find((collections: RemoteData>) => !collections.isResponsePending && collections.hasSucceeded), + mergeMap((collections: RemoteData>) => collections.payload.page), + filter((collectionData: Collection) => isNotEmpty(collectionData)), + map((collectionData: Collection) => ({ + communities: [{ id: communityData.id, name: communityData.name }], + collection: { id: collectionData.id, name: collectionData.name } + })) + ); + }), + reduce((acc: any, value: any) => [...acc, ...value], []), + startWith([]) + ); + + const searchTerm$ = this.searchField.valueChanges.pipe( + debounceTime(200), + distinctUntilChanged(), + startWith('') + ); + + this.searchListCollection$ = combineLatest(searchTerm$, listCollection$).pipe( + map(([searchTerm, listCollection]) => { + this.disabled$.next(isEmpty(listCollection)); + if (isEmpty(searchTerm)) { + return listCollection; + } else { + return listCollection.filter((v) => v.collection.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1).slice(0, 5); } - }) - }) - ); - - this.selectedCollectionName$ = listCollection$.pipe( - map((collectionData: CollectionListEntry[]) => collectionData.find((entry: CollectionListEntry) => entry.collection.id === this.selectedCollectionId)), - filter((entry: CollectionListEntry) => hasValue(entry.collection)), - map((entry: CollectionListEntry) => entry.collection.name), - startWith('') - ); - - const searchTerm$ = this.searchField.valueChanges.pipe( - debounceTime(200), - distinctUntilChanged(), - startWith('') - ); - - this.searchListCollection$ = combineLatest(searchTerm$, listCollection$).pipe( - map(([searchTerm, listCollection]) => { - this.disabled$.next(isEmpty(listCollection)); - if (isEmpty(searchTerm)) { - return listCollection; - } else { - return listCollection.filter((v) => v.collection.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1).slice(0, 5); - } - })); + })); + } } } @@ -271,7 +274,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { */ onSelect(event) { this.searchField.reset(); - this.disabled$.next(true); + this.processingChange$.next(true); this.operationsBuilder.replace(this.pathCombiner.getPath(), event.collection.id, true); this.subs.push(this.operationsService.jsonPatchByResourceID( this.submissionService.getSubmissionObjectLinkName(), @@ -283,7 +286,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { this.selectedCollectionName$ = observableOf(event.collection.name); this.collectionChange.emit(submissionObject[0]); this.submissionService.changeSubmissionCollection(this.submissionId, event.collection.id); - this.disabled$.next(false); + this.processingChange$.next(false); this.cdr.detectChanges(); }) ); diff --git a/src/app/submission/objects/submission-objects.reducer.ts b/src/app/submission/objects/submission-objects.reducer.ts index 1a65783945..8c111dde67 100644 --- a/src/app/submission/objects/submission-objects.reducer.ts +++ b/src/app/submission/objects/submission-objects.reducer.ts @@ -361,7 +361,7 @@ const addError = (state: SubmissionObjectState, action: InertSectionErrorsAction * @param state * the current state * @param action - * an RemoveSectionErrorsAction + * a RemoveSectionErrorsAction * @return SubmissionObjectState * the new state, with the section's errors updated. */ @@ -416,7 +416,7 @@ function initSubmission(state: SubmissionObjectState, action: InitSubmissionForm * @param state * the current state * @param action - * an ResetSubmissionFormAction + * a ResetSubmissionFormAction * @return SubmissionObjectState * the new state, with the section removed. */ @@ -439,7 +439,7 @@ function resetSubmission(state: SubmissionObjectState, action: ResetSubmissionFo * @param state * the current state * @param action - * an CompleteInitSubmissionFormAction + * a CompleteInitSubmissionFormAction * @return SubmissionObjectState * the new state, with the section removed. */ @@ -461,7 +461,7 @@ function completeInit(state: SubmissionObjectState, action: CompleteInitSubmissi * @param state * the current state * @param action - * an SaveSubmissionFormAction | SaveSubmissionSectionFormAction + * a SaveSubmissionFormAction | SaveSubmissionSectionFormAction * | SaveForLaterSubmissionFormAction | SaveAndDepositSubmissionAction * @return SubmissionObjectState * the new state, with the flag set to true. @@ -491,7 +491,7 @@ function saveSubmission(state: SubmissionObjectState, * @param state * the current state * @param action - * an SaveSubmissionFormSuccessAction | SaveForLaterSubmissionFormSuccessAction + * a SaveSubmissionFormSuccessAction | SaveForLaterSubmissionFormSuccessAction * | SaveSubmissionSectionFormSuccessAction | SaveSubmissionFormErrorAction * | SaveForLaterSubmissionFormErrorAction | SaveSubmissionSectionFormErrorAction * @return SubmissionObjectState @@ -521,7 +521,7 @@ function completeSave(state: SubmissionObjectState, * @param state * the current state * @param action - * an DepositSubmissionAction + * a DepositSubmissionAction * @return SubmissionObjectState * the new state, with the deposit flag changed. */ @@ -544,7 +544,7 @@ function startDeposit(state: SubmissionObjectState, action: DepositSubmissionAct * @param state * the current state * @param action - * an DepositSubmissionSuccessAction or DepositSubmissionErrorAction + * a DepositSubmissionSuccessAction or a DepositSubmissionErrorAction * @return SubmissionObjectState * the new state, with the deposit flag changed. */ @@ -586,7 +586,7 @@ function changeCollection(state: SubmissionObjectState, action: ChangeSubmission * @param state * the current state * @param action - * an SetActiveSectionAction + * a SetActiveSectionAction * @return SubmissionObjectState * the new state, with the active section. */ @@ -676,7 +676,7 @@ function updateSectionData(state: SubmissionObjectState, action: UpdateSectionDa * @param state * the current state * @param action - * an DisableSectionAction + * a DisableSectionAction * @param enabled * enabled or disabled section. * @return SubmissionObjectState @@ -705,7 +705,7 @@ function changeSectionState(state: SubmissionObjectState, action: EnableSectionA * @param state * the current state * @param action - * an SectionStatusChangeAction + * a SectionStatusChangeAction * @return SubmissionObjectState * the new state, with the section new validity status. */ @@ -769,7 +769,7 @@ function newFile(state: SubmissionObjectState, action: NewUploadedFileAction): S * @param state * the current state * @param action - * a EditFileDataAction action + * an EditFileDataAction action * @return SubmissionObjectState * the new state, with the edited file. */ diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index ef817a7568..2269ccd5f1 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -64,6 +64,12 @@ export class SubmissionSectionformComponent extends SectionModelComponent { */ public isLoading = true; + /** + * A map representing all field on their way to be removed + * @type {Map} + */ + protected fieldsOnTheirWayToBeRemoved: Map = new Map(); + /** * The form config * @type {SubmissionFormsModel} @@ -295,6 +301,7 @@ export class SubmissionSectionformComponent extends SectionModelComponent { }), distinctUntilChanged()) .subscribe((sectionState: SubmissionSectionObject) => { + this.fieldsOnTheirWayToBeRemoved = new Map(); this.updateForm(sectionState.data as WorkspaceitemSectionFormObject, sectionState.errors); }) ) @@ -348,11 +355,24 @@ export class SubmissionSectionformComponent extends SectionModelComponent { * the [[DynamicFormControlEvent]] emitted */ onRemove(event: DynamicFormControlEvent): void { + const fieldId = this.formBuilderService.getId(event.model); + const fieldIndex = this.formOperationsService.getArrayIndexFromEvent(event); + + // Keep track that this field will be removed + if (this.fieldsOnTheirWayToBeRemoved.has(fieldId)) { + const indexes = this.fieldsOnTheirWayToBeRemoved.get(fieldId); + indexes.push(fieldIndex); + this.fieldsOnTheirWayToBeRemoved.set(fieldId, indexes); + } else { + this.fieldsOnTheirWayToBeRemoved.set(fieldId, [fieldIndex]); + } + this.formOperationsService.dispatchOperationsFromEvent( this.pathCombiner, event, this.previousValue, - this.hasStoredValue(this.formBuilderService.getId(event.model), this.formOperationsService.getArrayIndexFromEvent(event))); + this.hasStoredValue(fieldId, fieldIndex)); + } /** @@ -365,9 +385,23 @@ export class SubmissionSectionformComponent extends SectionModelComponent { */ hasStoredValue(fieldId, index): boolean { if (isNotEmpty(this.sectionData.data)) { - return this.sectionData.data.hasOwnProperty(fieldId) && isNotEmpty(this.sectionData.data[fieldId][index]); + return this.sectionData.data.hasOwnProperty(fieldId) && + isNotEmpty(this.sectionData.data[fieldId][index]) && + !this.isFieldToRemove(fieldId, index); } else { return false; } } + + /** + * Check if the specified field is on the way to be removed + * + * @param fieldId + * the section data retrieved from the serverù + * @param index + * the section data retrieved from the server + */ + isFieldToRemove(fieldId, index) { + return this.fieldsOnTheirWayToBeRemoved.has(fieldId) && this.fieldsOnTheirWayToBeRemoved.get(fieldId).includes(index); + } } diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts index 826385af45..9dbd1079f4 100644 --- a/src/app/submission/sections/upload/section-upload.component.ts +++ b/src/app/submission/sections/upload/section-upload.component.ts @@ -155,14 +155,14 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent { filter((submissionObject: SubmissionObjectEntry) => isUndefined(this.collectionId) || this.collectionId !== submissionObject.collection), tap((submissionObject: SubmissionObjectEntry) => this.collectionId = submissionObject.collection), flatMap((submissionObject: SubmissionObjectEntry) => this.collectionDataService.findById(submissionObject.collection)), - find((rd: RemoteData) => isNotUndefined((rd.payload))), + filter((rd: RemoteData) => isNotUndefined((rd.payload))), tap((collectionRemoteData: RemoteData) => this.collectionName = collectionRemoteData.payload.name), flatMap((collectionRemoteData: RemoteData) => { return this.resourcePolicyService.findByHref( (collectionRemoteData.payload as any)._links.defaultAccessConditions ); }), - find((defaultAccessConditionsRemoteData: RemoteData) => + filter((defaultAccessConditionsRemoteData: RemoteData) => defaultAccessConditionsRemoteData.hasSucceeded), tap((defaultAccessConditionsRemoteData: RemoteData) => { if (isNotEmpty(defaultAccessConditionsRemoteData.payload)) { @@ -171,7 +171,6 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent { } }), flatMap(() => config$), - take(1), flatMap((config: SubmissionUploadsModel) => { this.availableAccessConditionOptions = isNotEmpty(config.accessConditionOptions) ? config.accessConditionOptions : []; diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index 0191b49e9b..7605f1c73a 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -197,7 +197,11 @@ export class SubmissionService { * The submission id */ dispatchSave(submissionId) { - this.store.dispatch(new SaveSubmissionFormAction(submissionId)); + this.getSubmissionSaveProcessingStatus(submissionId).pipe( + find((isPending: boolean) => !isPending) + ).subscribe(() => { + this.store.dispatch(new SaveSubmissionFormAction(submissionId)); + }) } /** diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index 0c3046ba68..02abf6449b 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -64,7 +64,7 @@ export function createTranslateLoader() { { provide: SubmissionService, useClass: ServerSubmissionService - }, + } ] }) export class ServerAppModule { diff --git a/src/server.ts b/src/server.ts index 0526f196ba..de0f8082bb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -67,7 +67,7 @@ export function startServer(bootstrap: Type<{}> | NgModuleFactory<{}>) { function onHandleError(parentZoneDelegate, currentZone, targetZone, error) { if (!res._headerSent) { - console.warn('Error in SSR, serving for direct CSR'); + console.warn('Error in SSR, serving for direct CSR. Error details : ', error); res.sendFile('index.csr.html', { root: './src' }); } } diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss index 8ca2067a2e..df193be91b 100644 --- a/src/styles/_custom_variables.scss +++ b/src/styles/_custom_variables.scss @@ -13,7 +13,7 @@ $drop-zone-area-inner-z-index: 1021; $login-logo-height:72px; $login-logo-width:72px; $submission-header-z-index: 1001; -$submission-footer-z-index: 1000; +$submission-footer-z-index: 999; $main-z-index: 0; $nav-z-index: 10;