+
+
+
+
+
{{fileName}} ({{fileData?.sizeBytes | dsFileSize}})
diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts
index e2f600654d..bb72439aab 100644
--- a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts
+++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts
@@ -66,7 +66,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
const fileName = '123456-test-upload.jpg';
const fileId = '123456-test-upload';
const fileData: any = mockUploadFiles[0];
- const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId, 'files', fileIndex);
+ const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId);
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
add: jasmine.createSpy('add'),
@@ -200,6 +200,23 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
expect(compAsAny.deleteFile).toHaveBeenCalled();
});
+ it('should delete primary if file we delete is primary', () => {
+ compAsAny.isPrimary = true;
+ compAsAny.pathCombiner = pathCombiner;
+ operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
+ compAsAny.deleteFile();
+ expect(operationsBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath('primary'));
+ expect(uploadService.updateFilePrimaryBitstream).toHaveBeenCalledWith(submissionId, sectionId, null);
+ });
+
+ it('should NOT delete primary if file we delete is NOT primary', () => {
+ compAsAny.isPrimary = false;
+ compAsAny.pathCombiner = pathCombiner;
+ operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
+ compAsAny.deleteFile();
+ expect(uploadService.updateFilePrimaryBitstream).not.toHaveBeenCalledTimes(1);
+ });
+
it('should delete file properly', () => {
compAsAny.pathCombiner = pathCombiner;
operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
@@ -208,7 +225,8 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
compAsAny.deleteFile();
expect(uploadService.removeUploadedFile).toHaveBeenCalledWith(submissionId, sectionId, fileId);
- expect(operationsBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath());
+ expect(operationsBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath(['files', fileIndex]));
+
expect(operationsService.jsonPatchByResourceID).toHaveBeenCalledWith(
'workspaceitems',
submissionId,
diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.ts b/src/app/submission/sections/upload/file/section-upload-file.component.ts
index 26fb9445cb..f95e8213ca 100644
--- a/src/app/submission/sections/upload/file/section-upload-file.component.ts
+++ b/src/app/submission/sections/upload/file/section-upload-file.component.ts
@@ -1,5 +1,4 @@
import {
- ChangeDetectorRef,
Component,
Input,
OnChanges,
@@ -9,7 +8,7 @@ import {
ViewChild
} from '@angular/core';
-import { BehaviorSubject, Subscription } from 'rxjs';
+import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { DynamicFormControlModel, } from '@ng-dynamic-forms/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
@@ -22,7 +21,6 @@ import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/buil
import { WorkspaceitemSectionUploadFileObject } from '../../../../core/submission/models/workspaceitem-section-upload-file.model';
import { SubmissionFormsModel } from '../../../../core/config/models/config-submission-forms.model';
import { SubmissionService } from '../../../submission.service';
-import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service';
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
import { Bitstream } from '../../../../core/shared/bitstream.model';
@@ -37,6 +35,12 @@ import { NgbModalOptions } from '@ng-bootstrap/ng-bootstrap/modal/modal-config';
templateUrl: './section-upload-file.component.html',
})
export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit, OnDestroy {
+ /**
+ * The indicator is the primary bitstream
+ * it will be null if no primary bitstream is set for the ORIGINAL bundle
+ * @type {boolean, null}
+ */
+ @Input() isPrimary: boolean | null;
/**
* The list of available access condition
@@ -100,6 +104,11 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
*/
@ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent;
+ /**
+ * A boolean representing if a submission save operation is pending
+ * @type {Observable
}
+ */
+ public processingSaveStatus$: Observable;
/**
* The bitstream's metadata data
@@ -137,6 +146,12 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
*/
protected pathCombiner: JsonPatchOperationPathCombiner;
+ /**
+ * The [JsonPatchOperationPathCombiner] object
+ * @type {JsonPatchOperationPathCombiner}
+ */
+ protected primaryBitstreamPathCombiner: JsonPatchOperationPathCombiner;
+
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
@@ -162,9 +177,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
* @param {SectionUploadService} uploadService
*/
constructor(
- private cdr: ChangeDetectorRef,
private formService: FormService,
- private halService: HALEndpointService,
private modalService: NgbModal,
private operationsBuilder: JsonPatchOperationsBuilder,
private operationsService: SubmissionJsonPatchOperationsService,
@@ -197,7 +210,8 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
*/
ngOnInit() {
this.formId = this.formService.getUniqueId(this.fileId);
- this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex);
+ this.processingSaveStatus$ = this.submissionService.getSubmissionSaveProcessingStatus(this.submissionId);
+ this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId);
this.loadFormMetadata();
}
@@ -247,7 +261,12 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
activeModal.componentInstance.formMetadata = this.formMetadata;
activeModal.componentInstance.pathCombiner = this.pathCombiner;
activeModal.componentInstance.submissionId = this.submissionId;
+ activeModal.componentInstance.isPrimary = this.isPrimary;
+ }
+ togglePrimaryBitstream(event) {
+ this.uploadService.updatePrimaryBitstreamOperation(this.pathCombiner.getPath('primary'), this.isPrimary, event.target.checked, this.fileId);
+ this.submissionService.dispatchSaveSection(this.submissionId, this.sectionId);
}
ngOnDestroy(): void {
@@ -273,13 +292,20 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
* Delete bitstream from submission
*/
protected deleteFile() {
- this.operationsBuilder.remove(this.pathCombiner.getPath());
+ this.operationsBuilder.remove(this.pathCombiner.getPath(['files', this.fileIndex]));
+ if (this.isPrimary) {
+ this.operationsBuilder.remove(this.pathCombiner.getPath('primary'));
+ }
+
this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
this.submissionService.getSubmissionObjectLinkName(),
this.submissionId,
this.pathCombiner.rootElement,
this.pathCombiner.subRootElement)
.subscribe(() => {
+ if (this.isPrimary) {
+ this.uploadService.updateFilePrimaryBitstream(this.submissionId, this.sectionId, null);
+ }
this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId);
this.processingDelete$.next(false);
}));
diff --git a/src/app/submission/sections/upload/file/themed-section-upload-file.component.ts b/src/app/submission/sections/upload/file/themed-section-upload-file.component.ts
index 9e0a265c3c..952813e03c 100644
--- a/src/app/submission/sections/upload/file/themed-section-upload-file.component.ts
+++ b/src/app/submission/sections/upload/file/themed-section-upload-file.component.ts
@@ -17,6 +17,13 @@ export class ThemedSubmissionSectionUploadFileComponent
*/
@Input() availableAccessConditionOptions: any[];
+ /**
+ * The indicator is the primary bitstream
+ * it will be null if no primary bitstream is set for the ORIGINAL bundle
+ * @type {boolean, null}
+ */
+ @Input() isPrimary: boolean | null;
+
/**
* The submission id
* @type {string}
@@ -69,6 +76,7 @@ export class ThemedSubmissionSectionUploadFileComponent
protected inAndOutputNames: (keyof SubmissionSectionUploadFileComponent & keyof this)[] = [
'availableAccessConditionOptions',
+ 'isPrimary',
'collectionId',
'collectionPolicyType',
'configMetadataForm',
diff --git a/src/app/submission/sections/upload/section-upload.component.html b/src/app/submission/sections/upload/section-upload.component.html
index b57b454288..41e912e613 100644
--- a/src/app/submission/sections/upload/section-upload.component.html
+++ b/src/app/submission/sections/upload/section-upload.component.html
@@ -2,15 +2,7 @@
[dismissible]="true"
[type]="AlertTypeEnum.Info">
-
-
-
-
{{'submission.sections.upload.no-file-uploaded' | translate}}
-
-
-
-
- 0">
+ 0; else noFileUploaded">
0" class="row">
@@ -26,16 +18,26 @@
-
-
+
+
+ {{ 'bitstream.edit.form.primaryBitstream.label' | translate }}
+
+
+
+
@@ -45,3 +47,11 @@
+
+
+
+
+
{{'submission.sections.upload.no-file-uploaded' | translate}}
+
+
+
diff --git a/src/app/submission/sections/upload/section-upload.component.spec.ts b/src/app/submission/sections/upload/section-upload.component.spec.ts
index 068fc5c766..eaa510a696 100644
--- a/src/app/submission/sections/upload/section-upload.component.spec.ts
+++ b/src/app/submission/sections/upload/section-upload.component.spec.ts
@@ -25,6 +25,7 @@ import {
mockUploadConfigResponse,
mockUploadConfigResponseNotRequired,
mockUploadFiles,
+ mockUploadFilesData,
} from '../../../shared/mocks/submission.mock';
import { SubmissionUploadsConfigDataService } from '../../../core/config/submission-uploads-config-data.service';
import { SectionUploadService } from './section-upload.service';
@@ -160,6 +161,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
);
bitstreamService.getUploadedFileList.and.returnValue(observableOf([]));
+ bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
};
TestBed.configureTestingModule({
@@ -230,7 +232,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
});
it('should init component properly', () => {
-
+ bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(Object.assign(new Collection(), mockCollection, {
@@ -246,15 +248,8 @@ describe('SubmissionSectionUploadComponent test suite', () => {
createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup))
);
- bitstreamService.getUploadedFileList.and.returnValue(observableOf([]));
-
comp.onSectionInit();
- const expectedGroupsMap = new Map([
- [mockUploadConfigResponse.accessConditionOptions[1].name, [mockGroup as any]],
- [mockUploadConfigResponse.accessConditionOptions[2].name, [mockGroup as any]],
- ]);
-
expect(comp.collectionId).toBe(collectionId);
expect(comp.collectionName).toBe(mockCollection.name);
expect(comp.availableAccessConditionOptions.length).toBe(4);
@@ -262,12 +257,12 @@ describe('SubmissionSectionUploadComponent test suite', () => {
expect(comp.required$.getValue()).toBe(true);
expect(compAsAny.subs.length).toBe(2);
expect(compAsAny.fileList).toEqual([]);
- expect(compAsAny.fileIndexes).toEqual([]);
expect(compAsAny.fileNames).toEqual([]);
-
+ expect(compAsAny.primaryBitstreamUUID).toEqual(null);
});
it('should init file list properly', () => {
+ bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
@@ -282,7 +277,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup))
);
- bitstreamService.getUploadedFileList.and.returnValue(observableOf(mockUploadFiles));
+ bitstreamService.getUploadedFilesData.and.returnValue(observableOf(mockUploadFilesData));
comp.onSectionInit();
@@ -298,12 +293,14 @@ describe('SubmissionSectionUploadComponent test suite', () => {
expect(comp.required$.getValue()).toBe(true);
expect(compAsAny.subs.length).toBe(2);
expect(compAsAny.fileList).toEqual(mockUploadFiles);
- expect(compAsAny.fileIndexes).toEqual(['123456-test-upload']);
+ expect(compAsAny.primaryBitstreamUUID).toEqual(null);
expect(compAsAny.fileNames).toEqual(['123456-test-upload.jpg']);
});
it('should properly read the section status when required is true', () => {
+ bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
+
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
@@ -324,7 +321,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
comp.onSectionInit();
- expect(comp.required$.getValue()).toBe(true);
+ expect(comp.required$.getValue()).toBe(true);
expect(compAsAny.getSectionStatus()).toBeObservable(cold('-c-d', {
c: false,
@@ -335,6 +332,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
it('should properly read the section status when required is false', () => {
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
+ bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
resourcePolicyService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(mockDefaultAccessCondition));
diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts
index 10203adbc0..ed64d514f9 100644
--- a/src/app/submission/sections/upload/section-upload.component.ts
+++ b/src/app/submission/sections/upload/section-upload.component.ts
@@ -4,7 +4,8 @@ import {
BehaviorSubject,
combineLatest as observableCombineLatest,
Observable,
- Subscription
+ Subscription,
+ combineLatest
} from 'rxjs';
import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators';
@@ -31,6 +32,7 @@ import { AccessConditionOption } from '../../../core/config/models/config-access
import { followLink } from '../../../shared/utils/follow-link-config.model';
import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
+import { WorkspaceitemSectionUploadObject } from 'src/app/core/submission/models/workspaceitem-section-upload.model';
export const POLICY_DEFAULT_NO_LIST = 1; // Banner1
export const POLICY_DEFAULT_WITH_LIST = 2; // Banner2
@@ -58,10 +60,10 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent {
public AlertTypeEnum = AlertType;
/**
- * The array containing the keys of file list array
+ * The uuid of primary bitstream file
* @type {Array}
*/
- public fileIndexes: string[] = [];
+ public primaryBitstreamUUID: string | null = null;
/**
* The file list
@@ -194,27 +196,18 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent {
this.changeDetectorRef.detectChanges();
}),
- // retrieve submission's bitstreams from state
- observableCombineLatest(this.configMetadataForm$,
- this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id)).pipe(
- filter(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => {
- return isNotEmpty(configMetadataForm) && isNotUndefined(fileList);
+
+ // retrieve submission's bitstream data from state
+ combineLatest([this.configMetadataForm$,
+ this.bitstreamService.getUploadedFilesData(this.submissionId, this.sectionData.id)]).pipe(
+ filter(([configMetadataForm, { files }]: [SubmissionFormsModel, WorkspaceitemSectionUploadObject]) => {
+ return isNotEmpty(configMetadataForm) && isNotEmpty(files);
}),
distinctUntilChanged())
- .subscribe(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => {
- this.fileList = [];
- this.fileIndexes = [];
- this.fileNames = [];
- this.changeDetectorRef.detectChanges();
- if (isNotUndefined(fileList) && fileList.length > 0) {
- fileList.forEach((file) => {
- this.fileList.push(file);
- this.fileIndexes.push(file.uuid);
- this.fileNames.push(this.getFileName(configMetadataForm, file));
- });
- }
-
- this.changeDetectorRef.detectChanges();
+ .subscribe(([configMetadataForm, { primary, files }]: [SubmissionFormsModel, WorkspaceitemSectionUploadObject]) => {
+ this.primaryBitstreamUUID = primary;
+ this.fileList = files;
+ this.fileNames = Array.from(files, file => this.getFileName(configMetadataForm, file));
}
)
);
diff --git a/src/app/submission/sections/upload/section-upload.service.spec.ts b/src/app/submission/sections/upload/section-upload.service.spec.ts
new file mode 100644
index 0000000000..e1d2abe048
--- /dev/null
+++ b/src/app/submission/sections/upload/section-upload.service.spec.ts
@@ -0,0 +1,63 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { TestBed, waitForAsync } from '@angular/core/testing';
+import { JsonPatchOperationPathCombiner } from 'src/app/core/json-patch/builder/json-patch-operation-path-combiner';
+import { JsonPatchOperationsBuilder } from 'src/app/core/json-patch/builder/json-patch-operations-builder';
+import { SectionUploadService } from './section-upload.service';
+import { Store, StoreModule } from '@ngrx/store';
+
+const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
+ add: jasmine.createSpy('add'),
+ replace: jasmine.createSpy('replace'),
+ remove: jasmine.createSpy('remove'),
+});
+
+describe('SectionUploadService test suite', () => {
+ let sectionUploadService: SectionUploadService;
+ let operationsBuilder: any;
+ const pathCombiner = new JsonPatchOperationPathCombiner('sections', 'upload');
+ const primaryPath = pathCombiner.getPath('primary');
+ const fileId = 'test';
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [StoreModule],
+ providers: [
+ { provide: Store, useValue: {} },
+ SectionUploadService,
+ { provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder },
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ });
+ }));
+
+ beforeEach(() => {
+ sectionUploadService = TestBed.inject(SectionUploadService);
+ operationsBuilder = TestBed.inject(JsonPatchOperationsBuilder);
+ });
+
+ [
+ {
+ initialPrimary: null,
+ primary: true,
+ operationName: 'add',
+ expected: [primaryPath, fileId, false, true]
+ },
+ {
+ initialPrimary: true,
+ primary: false,
+ operationName: 'remove',
+ expected: [primaryPath]
+ },
+ {
+ initialPrimary: false,
+ primary: true,
+ operationName: 'replace',
+ expected: [primaryPath, fileId, true]
+ }
+ ].forEach(({ initialPrimary, primary, operationName, expected }) => {
+ it(`updatePrimaryBitstreamOperation should add ${operationName} operation`, () => {
+ const path = pathCombiner.getPath('primary');
+ sectionUploadService.updatePrimaryBitstreamOperation(path, initialPrimary, primary, fileId);
+ expect(operationsBuilder[operationName]).toHaveBeenCalledWith(...expected);
+ });
+ });
+});
diff --git a/src/app/submission/sections/upload/section-upload.service.ts b/src/app/submission/sections/upload/section-upload.service.ts
index a851fa9daf..ef32ea4cfd 100644
--- a/src/app/submission/sections/upload/section-upload.service.ts
+++ b/src/app/submission/sections/upload/section-upload.service.ts
@@ -8,11 +8,15 @@ import { SubmissionState } from '../../submission.reducers';
import {
DeleteUploadedFileAction,
EditFileDataAction,
+ EditFilePrimaryBitstreamAction,
NewUploadedFileAction
} from '../../objects/submission-objects.actions';
-import { submissionUploadedFileFromUuidSelector, submissionUploadedFilesFromIdSelector } from '../../selectors';
+import { submissionSectionDataFromIdSelector, submissionUploadedFileFromUuidSelector, submissionUploadedFilesFromIdSelector } from '../../selectors';
import { isUndefined } from '../../../shared/empty.util';
import { WorkspaceitemSectionUploadFileObject } from '../../../core/submission/models/workspaceitem-section-upload-file.model';
+import { WorkspaceitemSectionUploadObject } from 'src/app/core/submission/models/workspaceitem-section-upload.model';
+import { JsonPatchOperationPathObject } from 'src/app/core/json-patch/builder/json-patch-operation-path-combiner';
+import { JsonPatchOperationsBuilder } from 'src/app/core/json-patch/builder/json-patch-operations-builder';
/**
* A service that provides methods to handle submission's bitstream state.
@@ -24,8 +28,53 @@ export class SectionUploadService {
* Initialize service variables
*
* @param {Store} store
+ * @param {JsonPatchOperationsBuilder} operationsBuilder
*/
- constructor(private store: Store) {}
+ constructor(private store: Store, private operationsBuilder: JsonPatchOperationsBuilder) {}
+
+ /**
+ * Define and add an operation based on a change
+ *
+ * @param path
+ * The path to endpoint
+ * @param intitialPrimary
+ * The initial primary indicator
+ * @param primary
+ * the new primary indicator
+ * @param fileId
+ * The file id
+ * @returns {void}
+ */
+ public updatePrimaryBitstreamOperation(path: JsonPatchOperationPathObject, intitialPrimary: boolean | null, primary: boolean | null, fileId: string): void {
+ if (intitialPrimary === null && primary) {
+ this.operationsBuilder.add(path, fileId, false, true);
+ return;
+ }
+
+ if (intitialPrimary !== primary) {
+ if (primary) {
+ this.operationsBuilder.replace(path, fileId, true);
+ return;
+ }
+ this.operationsBuilder.remove(path);
+ }
+ }
+
+ /**
+ * Return submission's bitstream data from state
+ *
+ * @param submissionId
+ * The submission id
+ * @param sectionId
+ * The section id
+ * @returns {WorkspaceitemSectionUploadObject}
+ * Returns submission's bitstream data
+ */
+ public getUploadedFilesData(submissionId: string, sectionId: string): Observable {
+ return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)).pipe(
+ map((state) => state),
+ distinctUntilChanged());
+ }
/**
* Return submission's bitstream list from state
@@ -104,6 +153,22 @@ export class SectionUploadService {
);
}
+ /**
+ * Update primary bitstream into the state
+ *
+ * @param submissionId
+ * The submission id
+ * @param sectionId
+ * The section id
+ * @param fileUUID
+ * The bitstream UUID
+ */
+ public updateFilePrimaryBitstream(submissionId: string, sectionId: string, fileUUID: string | null) {
+ this.store.dispatch(
+ new EditFilePrimaryBitstreamAction(submissionId, sectionId, fileUUID)
+ );
+ }
+
/**
* Update bitstream metadata into the state
*
diff --git a/src/app/suggestion-notifications/suggestions.service.ts b/src/app/suggestion-notifications/suggestions.service.ts
index 874659eab5..3b2ce081f1 100644
--- a/src/app/suggestion-notifications/suggestions.service.ts
+++ b/src/app/suggestion-notifications/suggestions.service.ts
@@ -11,7 +11,7 @@ import { hasValue, isNotEmpty } from '../shared/empty.util';
import { ResearcherProfile } from '../core/profile/model/researcher-profile.model';
import {
getAllSucceededRemoteDataPayload,
- getFinishedRemoteData,
+ getFinishedRemoteData, getFirstCompletedRemoteData,
getFirstSucceededRemoteDataPayload,
getFirstSucceededRemoteListPayload
} from '../core/shared/operators';
@@ -155,10 +155,10 @@ export class SuggestionsService {
*/
public retrieveCurrentUserSuggestions(userUuid: string): Observable {
return this.researcherProfileService.findById(userUuid, true).pipe(
- getFirstSucceededRemoteDataPayload(),
- mergeMap((profile: ResearcherProfile) => {
- if (isNotEmpty(profile)) {
- return this.researcherProfileService.findRelatedItemId(profile).pipe(
+ getFirstCompletedRemoteData(),
+ mergeMap((profile: RemoteData ) => {
+ if (isNotEmpty(profile) && profile.hasSucceeded && isNotEmpty(profile.payload)) {
+ return this.researcherProfileService.findRelatedItemId(profile.payload).pipe(
mergeMap((itemId: string) => {
return this.suggestionsDataService.getTargetsByUser(itemId).pipe(
getFirstSucceededRemoteListPayload()
@@ -169,7 +169,7 @@ export class SuggestionsService {
return of([]);
}
}),
- take(1)
+ catchError(() => of([]))
);
}
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index e20a724158..57ed7d3067 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -726,7 +726,7 @@
"bitstream.edit.form.newFormat.hint": "The application you used to create the file, and the version number (for example, \"ACMESoft SuperApp version 1.5\").",
- "bitstream.edit.form.primaryBitstream.label": "Primary bitstream",
+ "bitstream.edit.form.primaryBitstream.label": "Primary File",
"bitstream.edit.form.selectedFormat.hint": "If the format is not in the above list, select \"format not in list\" above and describe it under \"Describe new format\".",
@@ -1108,9 +1108,7 @@
"collection.logo": "Collection logo",
- "collection.page.browse.recent.head": "Recent Submissions",
-
- "collection.page.browse.recent.empty": "No items to show",
+ "collection.page.browse.search.head": "Search",
"collection.page.edit": "Edit this collection",
@@ -1120,6 +1118,8 @@
"collection.page.news": "News",
+ "collection.search.results.head": "Search Results",
+
"collection.select.confirm": "Confirm selected",
"collection.select.empty": "No collections to show",
@@ -1196,6 +1196,8 @@
"community.browse.logo": "Browse for a community logo",
+ "community.subcoms-cols.breadcrumbs": "Subcommunities and Collections",
+
"community.create.head": "Create a Community",
"community.create.notifications.success": "Successfully created the Community",
@@ -1348,6 +1350,8 @@
"community.all-lists.head": "Subcommunities and Collections",
+ "community.search.results.head": "Search Results",
+
"community.sub-collection-list.head": "Collections in this Community",
"community.sub-community-list.head": "Communities in this Community",
@@ -2148,6 +2152,8 @@
"item.edit.metadata.edit.value": "Edit value",
+ "item.edit.metadata.edit.authority.key": "Edit authority key",
+
"item.edit.metadata.edit.buttons.confirm": "Confirm",
"item.edit.metadata.edit.buttons.drag": "Drag to reorder",
@@ -2202,6 +2208,12 @@
"item.edit.metadata.save-button": "Save",
+ "item.edit.metadata.authority.label": "Authority: ",
+
+ "item.edit.metadata.edit.buttons.open-authority-edition": "Unlock the authority key value for manual editing",
+
+ "item.edit.metadata.edit.buttons.close-authority-edition": "Lock the authority key value for manual editing",
+
"item.edit.modify.overview.field": "Field",
"item.edit.modify.overview.language": "Language",
@@ -2436,6 +2448,24 @@
"workflow-item.search.result.list.element.supervised.remove-tooltip": "Remove supervision group",
+ "confidence.indicator.help-text.accepted": "This authority value has been confirmed as accurate by an interactive user",
+
+ "confidence.indicator.help-text.uncertain": "Value is singular and valid but has not been seen and accepted by a human so it is still uncertain",
+
+ "confidence.indicator.help-text.ambiguous": "There are multiple matching authority values of equal validity",
+
+ "confidence.indicator.help-text.notfound": "There are no matching answers in the authority",
+
+ "confidence.indicator.help-text.failed": "The authority encountered an internal failure",
+
+ "confidence.indicator.help-text.rejected": "The authority recommends this submission be rejected",
+
+ "confidence.indicator.help-text.novalue": "No reasonable confidence value was returned from the authority",
+
+ "confidence.indicator.help-text.unset": "Confidence was never recorded for this value",
+
+ "confidence.indicator.help-text.unknown": "Unknown confidence value",
+
"item.page.abstract": "Abstract",
"item.page.author": "Authors",
@@ -2496,6 +2526,8 @@
"item.page.bitstreams.collapse": "Collapse",
+ "item.page.bitstreams.primary": "Primary",
+
"item.page.filesection.original.bundle": "Original bundle",
"item.page.filesection.license.bundle": "License bundle",
@@ -3470,12 +3502,32 @@
"process.detail.delete.error": "Something went wrong when deleting the process",
+ "process.detail.refreshing": "Auto-refreshing…",
+
+ "process.overview.table.completed.info": "Finish time (UTC)",
+
+ "process.overview.table.completed.title": "Succeeded processes",
+
+ "process.overview.table.empty": "No matching processes found.",
+
+ "process.overview.table.failed.info": "Finish time (UTC)",
+
+ "process.overview.table.failed.title": "Failed processes",
+
"process.overview.table.finish": "Finish time (UTC)",
"process.overview.table.id": "Process ID",
"process.overview.table.name": "Name",
+ "process.overview.table.running.info": "Start time (UTC)",
+
+ "process.overview.table.running.title": "Running processes",
+
+ "process.overview.table.scheduled.info": "Creation time (UTC)",
+
+ "process.overview.table.scheduled.title": "Scheduled processes",
+
"process.overview.table.start": "Start time (UTC)",
"process.overview.table.status": "Status",
@@ -4350,6 +4402,8 @@
"submission.import-external.source.datacite": "DataCite",
+ "submission.import-external.source.doi": "DOI",
+
"submission.import-external.source.scielo": "SciELO",
"submission.import-external.source.scopus": "Scopus",
@@ -4818,6 +4872,10 @@
"submission.sections.toggle.aria.close": "Collapse {{sectionHeader}} section",
+ "submission.sections.upload.primary.make": "Make {{fileName}} the primary bitstream",
+
+ "submission.sections.upload.primary.remove": "Remove {{fileName}} as the primary bitstream",
+
"submission.sections.upload.delete.confirm.cancel": "Cancel",
"submission.sections.upload.delete.confirm.info": "This operation can't be undone. Are you sure?",
@@ -5572,6 +5630,8 @@
"admin.system-wide-alert.title": "System-wide Alerts",
+ "discover.filters.head": "Discover",
+
"item-access-control-title": "This form allows you to perform changes to the access conditions of the item's metadata or its bitstreams.",
"collection-access-control-title": "This form allows you to perform changes to the access conditions of all the items owned by this collection. Changes may be performed to either all Item metadata or all content (bitstreams).",
@@ -5640,3 +5700,4 @@
"admin.notifications.publicationclaim.page.title": "Publication Claim",
}
+
diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts
index 6c4b99cb0f..51a116fa70 100644
--- a/src/config/app-config.interface.ts
+++ b/src/config/app-config.interface.ts
@@ -8,6 +8,7 @@ import { SubmissionConfig } from './submission-config.interface';
import { FormConfig } from './form-config.interfaces';
import { LangConfig } from './lang-config.interface';
import { ItemConfig } from './item-config.interface';
+import { CommunityPageConfig } from './community-page-config.interface';
import { CollectionPageConfig } from './collection-page-config.interface';
import { ThemeConfig } from './theme.config';
import { AuthConfig } from './auth-config.interfaces';
@@ -41,6 +42,7 @@ interface AppConfig extends Config {
communityList: CommunityListConfig;
homePage: HomeConfig;
item: ItemConfig;
+ community: CommunityPageConfig;
collection: CollectionPageConfig;
themes: ThemeConfig[];
mediaViewer: MediaViewerConfig;
diff --git a/src/config/collection-page-config.interface.ts b/src/config/collection-page-config.interface.ts
index c056df66ed..6b8352e686 100644
--- a/src/config/collection-page-config.interface.ts
+++ b/src/config/collection-page-config.interface.ts
@@ -1,7 +1,18 @@
import { Config } from './config.interface';
+/**
+ * Collection Page Config
+ */
export interface CollectionPageConfig extends Config {
+ searchSection: CollectionSearchSectionConfig;
edit: {
undoTimeout: number;
};
}
+
+/**
+ * Config related to the collection's search tab
+ */
+export interface CollectionSearchSectionConfig {
+ showSidebar: boolean;
+}
diff --git a/src/config/community-page-config.interface.ts b/src/config/community-page-config.interface.ts
new file mode 100644
index 0000000000..268f4d6a5e
--- /dev/null
+++ b/src/config/community-page-config.interface.ts
@@ -0,0 +1,15 @@
+import { Config } from './config.interface';
+
+/**
+ * Community Page Config
+ */
+export interface CommunityPageConfig extends Config {
+ searchSection: CommunitySearchSectionConfig;
+}
+
+/**
+ * Config related to the community's search tab
+ */
+export interface CommunitySearchSectionConfig {
+ showSidebar: boolean;
+}
diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts
index 3b3eb00ddb..ae5c6c1d00 100644
--- a/src/config/default-app-config.ts
+++ b/src/config/default-app-config.ts
@@ -23,6 +23,7 @@ import { HomeConfig } from './homepage-config.interface';
import { MarkdownConfig } from './markdown-config.interface';
import { FilterVocabularyConfig } from './filter-vocabulary-config';
import { DiscoverySortConfig } from './discovery-sort.config';
+import { CommunityPageConfig } from './community-page-config.interface';
import {QualityAssuranceConfig} from './quality-assurance.config';
export class DefaultAppConfig implements AppConfig {
@@ -163,7 +164,7 @@ export class DefaultAppConfig implements AppConfig {
* {
* // NOTE: metadata name
* name: 'dc.author',
- * // NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used
+ * // NOTE: fontawesome (v6.x) icon classes and bootstrap utility classes can be used
* style: 'fa-user'
* }
*/
@@ -183,27 +184,59 @@ export class DefaultAppConfig implements AppConfig {
* NOTE: example of configuration
* {
* // NOTE: confidence value
- * value: 'dc.author',
- * // NOTE: fontawesome (v4.x) icon classes and bootstrap utility classes can be used
- * style: 'fa-user'
+ * value: 100,
+ * // NOTE: fontawesome (v6.x) icon classes and bootstrap utility classes can be used
+ * style: 'text-success',
+ * icon: 'fa-circle-check'
+ * // NOTE: the class configured in property style is used by default, the icon property could be used in component
+ * // configured to use a 'icon mode' display (mainly in edit-item page)
* }
*/
{
value: 600,
- style: 'text-success'
+ style: 'text-success',
+ icon: 'fa-circle-check'
},
{
value: 500,
- style: 'text-info'
+ style: 'text-info',
+ icon: 'fa-gear'
},
{
value: 400,
- style: 'text-warning'
+ style: 'text-warning',
+ icon: 'fa-circle-question'
+ },
+ {
+ value: 300,
+ style: 'text-muted',
+ icon: 'fa-circle-question'
+ },
+ {
+ value: 200,
+ style: 'text-muted',
+ icon: 'fa-circle-exclamation'
+ },
+ {
+ value: 100,
+ style: 'text-muted',
+ icon: 'fa-circle-stop'
+ },
+ {
+ value: 0,
+ style: 'text-muted',
+ icon: 'fa-ban'
+ },
+ {
+ value: -1,
+ style: 'text-muted',
+ icon: 'fa-circle-xmark'
},
// default configuration
{
value: 'default',
- style: 'text-muted'
+ style: 'text-muted',
+ icon: 'fa-circle-xmark'
}
]
@@ -273,7 +306,8 @@ export class DefaultAppConfig implements AppConfig {
},
topLevelCommunityList: {
pageSize: 5
- }
+ },
+ showDiscoverFilters: false
};
// Item Config
@@ -291,8 +325,18 @@ export class DefaultAppConfig implements AppConfig {
}
};
+ // Community Page Config
+ community: CommunityPageConfig = {
+ searchSection: {
+ showSidebar: true,
+ },
+ };
+
// Collection Page Config
collection: CollectionPageConfig = {
+ searchSection: {
+ showSidebar: true,
+ },
edit: {
undoTimeout: 10000 // 10 seconds
}
diff --git a/src/config/homepage-config.interface.ts b/src/config/homepage-config.interface.ts
index df5d29cfe0..48fd9077a9 100644
--- a/src/config/homepage-config.interface.ts
+++ b/src/config/homepage-config.interface.ts
@@ -1,7 +1,7 @@
import { Config } from './config.interface';
/**
- * Config that determines how the dropdown list of years are created for browse-by-date components
+ * Config that determines how the recentSubmissions list showing at home page
*/
export interface HomeConfig extends Config {
recentSubmissions: {
@@ -19,4 +19,8 @@ export interface HomeConfig extends Config {
topLevelCommunityList: {
pageSize: number;
};
+ /*
+ * Enable or disable the Discover filters on the homepage
+ */
+ showDiscoverFilters: boolean;
}
diff --git a/src/config/submission-config.interface.ts b/src/config/submission-config.interface.ts
index a63af45e38..b0d1df900f 100644
--- a/src/config/submission-config.interface.ts
+++ b/src/config/submission-config.interface.ts
@@ -24,6 +24,7 @@ export interface MetadataIconConfig extends Config {
export interface ConfidenceIconConfig extends Config {
value: any;
style: string;
+ icon: string;
}
export interface SubmissionConfig extends Config {
diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts
index afc4082dde..e0cf1eb207 100644
--- a/src/environments/environment.test.ts
+++ b/src/environments/environment.test.ts
@@ -147,19 +147,23 @@ export const environment: BuildConfig = {
confidence: [
{
value: 600,
- style: 'text-success'
+ style: 'text-success',
+ icon: 'fa-circle-check'
},
{
value: 500,
- style: 'text-info'
+ style: 'text-info',
+ icon: 'fa-gear'
},
{
value: 400,
- style: 'text-warning'
+ style: 'text-warning',
+ icon: 'fa-circle-question'
},
{
value: 'default',
- style: 'text-muted'
+ style: 'text-muted',
+ icon: 'fa-circle-xmark'
},
]
}
@@ -242,7 +246,8 @@ export const environment: BuildConfig = {
},
topLevelCommunityList: {
pageSize: 5
- }
+ },
+ showDiscoverFilters: false
},
item: {
edit: {
@@ -257,7 +262,15 @@ export const environment: BuildConfig = {
pageSize: 5
}
},
+ community: {
+ searchSection: {
+ showSidebar: true,
+ },
+ },
collection: {
+ searchSection: {
+ showSidebar: true,
+ },
edit: {
undoTimeout: 10000 // 10 seconds
}
diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss
index 48f567b61b..7d2f80d451 100644
--- a/src/styles/_custom_variables.scss
+++ b/src/styles/_custom_variables.scss
@@ -108,4 +108,10 @@
--ds-item-page-img-field-default-inline-height: 24px;
+ --ds-process-overview-table-nb-processes-badge-size: 0.5em;
+ --ds-process-overview-table-id-column-width: 120px;
+ --ds-process-overview-table-name-column-width: auto;
+ --ds-process-overview-table-user-column-width: 200px;
+ --ds-process-overview-table-info-column-width: 250px;
+ --ds-process-overview-table-actions-column-width: 80px;
}