mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'main' of github.com:DSpace/dspace-angular; branch '1422-deploy-time-config' of github.com:wwelling/dspace-angular into 1422-deploy-time-config
This commit is contained in:
@@ -12,7 +12,7 @@ import { ActivatedRoute, Params, Router } from '@angular/router';
|
|||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
||||||
import { BrowseByType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
@@ -29,13 +29,13 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options
|
|||||||
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
||||||
* An example would be 'dateissued' for 'dc.date.issued'
|
* An example would be 'dateissued' for 'dc.date.issued'
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByType.Date)
|
@rendersBrowseBy(BrowseByDataType.Date)
|
||||||
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default metadata-field to use for determining the lower limit of the StartsWith dropdown options
|
* The default metadata keys to use for determining the lower limit of the StartsWith dropdown options
|
||||||
*/
|
*/
|
||||||
defaultMetadataField = 'dc.date.issued';
|
defaultMetadataKeys = ['dc.date.issued'];
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(protected route: ActivatedRoute,
|
||||||
protected browseService: BrowseService,
|
protected browseService: BrowseService,
|
||||||
@@ -59,13 +59,13 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort];
|
return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort];
|
||||||
})
|
})
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
||||||
const metadataField = params.metadataField || this.defaultMetadataField;
|
const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys;
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
||||||
this.updatePageWithItems(searchOptions, this.value);
|
this.updatePageWithItems(searchOptions, this.value);
|
||||||
this.updateParent(params.scope);
|
this.updateParent(params.scope);
|
||||||
this.updateStartsWithOptions(this.browseId, metadataField, params.scope);
|
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,15 +76,15 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
* extremely long lists with a one-year difference.
|
* extremely long lists with a one-year difference.
|
||||||
* To determine the change in years, the config found under GlobalConfig.BrowseBy is used for this.
|
* To determine the change in years, the config found under GlobalConfig.BrowseBy is used for this.
|
||||||
* @param definition The metadata definition to fetch the first item for
|
* @param definition The metadata definition to fetch the first item for
|
||||||
* @param metadataField The metadata field to fetch the earliest date from (expects a date field)
|
* @param metadataKeys The metadata fields to fetch the earliest date from (expects a date field)
|
||||||
* @param scope The scope under which to fetch the earliest item for
|
* @param scope The scope under which to fetch the earliest item for
|
||||||
*/
|
*/
|
||||||
updateStartsWithOptions(definition: string, metadataField: string, scope?: string) {
|
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => {
|
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => {
|
||||||
let lowerLimit = environment.browseBy.defaultLowerLimit;
|
let lowerLimit = environment.browseBy.defaultLowerLimit;
|
||||||
if (hasValue(firstItemRD.payload)) {
|
if (hasValue(firstItemRD.payload)) {
|
||||||
const date = firstItemRD.payload.firstMetadataValue(metadataField);
|
const date = firstItemRD.payload.firstMetadataValue(metadataKeys);
|
||||||
if (hasValue(date)) {
|
if (hasValue(date)) {
|
||||||
const dateObj = new Date(date);
|
const dateObj = new Date(date);
|
||||||
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
||||||
@@ -120,5 +120,4 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,25 @@
|
|||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import { BrowseByGuard } from './browse-by-guard';
|
import { BrowseByGuard } from './browse-by-guard';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
|
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||||
|
import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator';
|
||||||
|
|
||||||
describe('BrowseByGuard', () => {
|
describe('BrowseByGuard', () => {
|
||||||
describe('canActivate', () => {
|
describe('canActivate', () => {
|
||||||
let guard: BrowseByGuard;
|
let guard: BrowseByGuard;
|
||||||
let dsoService: any;
|
let dsoService: any;
|
||||||
let translateService: any;
|
let translateService: any;
|
||||||
|
let browseDefinitionService: any;
|
||||||
|
|
||||||
const name = 'An interesting DSO';
|
const name = 'An interesting DSO';
|
||||||
const title = 'Author';
|
const title = 'Author';
|
||||||
const field = 'Author';
|
const field = 'Author';
|
||||||
const id = 'author';
|
const id = 'author';
|
||||||
const metadataField = 'dc.contributor';
|
|
||||||
const scope = '1234-65487-12354-1235';
|
const scope = '1234-65487-12354-1235';
|
||||||
const value = 'Filter';
|
const value = 'Filter';
|
||||||
|
const browseDefinition = Object.assign(new BrowseDefinition(), { type: BrowseByDataType.Metadata, metadataKeys: ['dc.contributor'] });
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
dsoService = {
|
dsoService = {
|
||||||
@@ -24,14 +29,19 @@ describe('BrowseByGuard', () => {
|
|||||||
translateService = {
|
translateService = {
|
||||||
instant: () => field
|
instant: () => field
|
||||||
};
|
};
|
||||||
guard = new BrowseByGuard(dsoService, translateService);
|
|
||||||
|
browseDefinitionService = {
|
||||||
|
findById: () => createSuccessfulRemoteDataObject$(browseDefinition)
|
||||||
|
};
|
||||||
|
|
||||||
|
guard = new BrowseByGuard(dsoService, translateService, browseDefinitionService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
||||||
const scopedRoute = {
|
const scopedRoute = {
|
||||||
data: {
|
data: {
|
||||||
title: field,
|
title: field,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
@@ -48,7 +58,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const result = {
|
const result = {
|
||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
collection: name,
|
collection: name,
|
||||||
field,
|
field,
|
||||||
value: '"' + value + '"'
|
value: '"' + value + '"'
|
||||||
@@ -63,7 +73,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const scopedNoValueRoute = {
|
const scopedNoValueRoute = {
|
||||||
data: {
|
data: {
|
||||||
title: field,
|
title: field,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
@@ -80,7 +90,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const result = {
|
const result = {
|
||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
collection: name,
|
collection: name,
|
||||||
field,
|
field,
|
||||||
value: ''
|
value: ''
|
||||||
@@ -95,7 +105,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const route = {
|
const route = {
|
||||||
data: {
|
data: {
|
||||||
title: field,
|
title: field,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
@@ -111,7 +121,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const result = {
|
const result = {
|
||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
collection: '',
|
collection: '',
|
||||||
field,
|
field,
|
||||||
value: '"' + value + '"'
|
value: '"' + value + '"'
|
||||||
|
@@ -2,11 +2,12 @@ import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angul
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
|
||||||
import { hasNoValue, hasValue } from '../shared/empty.util';
|
import { hasNoValue, hasValue } from '../shared/empty.util';
|
||||||
import { map } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { getFirstSucceededRemoteData } from '../core/shared/operators';
|
import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { environment } from '../../environments/environment';
|
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
||||||
|
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
/**
|
/**
|
||||||
@@ -15,42 +16,46 @@ import { environment } from '../../environments/environment';
|
|||||||
export class BrowseByGuard implements CanActivate {
|
export class BrowseByGuard implements CanActivate {
|
||||||
|
|
||||||
constructor(protected dsoService: DSpaceObjectDataService,
|
constructor(protected dsoService: DSpaceObjectDataService,
|
||||||
protected translate: TranslateService) {
|
protected translate: TranslateService,
|
||||||
|
protected browseDefinitionService: BrowseDefinitionDataService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||||
const title = route.data.title;
|
const title = route.data.title;
|
||||||
const id = route.params.id || route.queryParams.id || route.data.id;
|
const id = route.params.id || route.queryParams.id || route.data.id;
|
||||||
let metadataField = route.data.metadataField;
|
let browseDefinition$: Observable<BrowseDefinition>;
|
||||||
if (hasNoValue(metadataField) && hasValue(id)) {
|
if (hasNoValue(route.data.browseDefinition) && hasValue(id)) {
|
||||||
const config = environment.browseBy.types.find((conf) => conf.id === id);
|
browseDefinition$ = this.browseDefinitionService.findById(id).pipe(getFirstSucceededRemoteDataPayload());
|
||||||
if (hasValue(config) && hasValue(config.metadataField)) {
|
} else {
|
||||||
metadataField = config.metadataField;
|
browseDefinition$ = observableOf(route.data.browseDefinition);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const scope = route.queryParams.scope;
|
const scope = route.queryParams.scope;
|
||||||
const value = route.queryParams.value;
|
const value = route.queryParams.value;
|
||||||
const metadataTranslated = this.translate.instant('browse.metadata.' + id);
|
const metadataTranslated = this.translate.instant('browse.metadata.' + id);
|
||||||
if (hasValue(scope)) {
|
return browseDefinition$.pipe(
|
||||||
const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteData());
|
switchMap((browseDefinition) => {
|
||||||
return dsoAndMetadata$.pipe(
|
if (hasValue(scope)) {
|
||||||
map((dsoRD) => {
|
const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteData());
|
||||||
const name = dsoRD.payload.name;
|
return dsoAndMetadata$.pipe(
|
||||||
route.data = this.createData(title, id, metadataField, name, metadataTranslated, value, route);
|
map((dsoRD) => {
|
||||||
return true;
|
const name = dsoRD.payload.name;
|
||||||
})
|
route.data = this.createData(title, id, browseDefinition, name, metadataTranslated, value, route);
|
||||||
);
|
return true;
|
||||||
} else {
|
})
|
||||||
route.data = this.createData(title, id, metadataField, '', metadataTranslated, value, route);
|
);
|
||||||
return observableOf(true);
|
} else {
|
||||||
}
|
route.data = this.createData(title, id, browseDefinition, '', metadataTranslated, value, route);
|
||||||
|
return observableOf(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createData(title, id, metadataField, collection, field, value, route) {
|
private createData(title, id, browseDefinition, collection, field, value, route) {
|
||||||
return Object.assign({}, route.data, {
|
return Object.assign({}, route.data, {
|
||||||
title: title,
|
title: title,
|
||||||
id: id,
|
id: id,
|
||||||
metadataField: metadataField,
|
browseDefinition: browseDefinition,
|
||||||
collection: collection,
|
collection: collection,
|
||||||
field: field,
|
field: field,
|
||||||
value: hasValue(value) ? `"${value}"` : ''
|
value: hasValue(value) ? `"${value}"` : ''
|
||||||
|
@@ -14,7 +14,7 @@ import { getFirstSucceededRemoteData } from '../../core/shared/operators';
|
|||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
||||||
import { BrowseByType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ import { map } from 'rxjs/operators';
|
|||||||
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
||||||
* An example would be 'author' for 'dc.contributor.*'
|
* An example would be 'author' for 'dc.contributor.*'
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByType.Metadata)
|
@rendersBrowseBy(BrowseByDataType.Metadata)
|
||||||
export class BrowseByMetadataPageComponent implements OnInit {
|
export class BrowseByMetadataPageComponent implements OnInit {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { BrowseByType, rendersBrowseBy } from './browse-by-decorator';
|
import { BrowseByDataType, rendersBrowseBy } from './browse-by-decorator';
|
||||||
|
|
||||||
describe('BrowseByDecorator', () => {
|
describe('BrowseByDecorator', () => {
|
||||||
const titleDecorator = rendersBrowseBy(BrowseByType.Title);
|
const titleDecorator = rendersBrowseBy(BrowseByDataType.Title);
|
||||||
const dateDecorator = rendersBrowseBy(BrowseByType.Date);
|
const dateDecorator = rendersBrowseBy(BrowseByDataType.Date);
|
||||||
const metadataDecorator = rendersBrowseBy(BrowseByType.Metadata);
|
const metadataDecorator = rendersBrowseBy(BrowseByDataType.Metadata);
|
||||||
it('should have a decorator for all types', () => {
|
it('should have a decorator for all types', () => {
|
||||||
expect(titleDecorator.length).not.toEqual(0);
|
expect(titleDecorator.length).not.toEqual(0);
|
||||||
expect(dateDecorator.length).not.toEqual(0);
|
expect(dateDecorator.length).not.toEqual(0);
|
||||||
|
@@ -2,13 +2,13 @@ import { hasNoValue } from '../../shared/empty.util';
|
|||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
|
|
||||||
export enum BrowseByType {
|
export enum BrowseByDataType {
|
||||||
Title = 'title',
|
Title = 'title',
|
||||||
Metadata = 'metadata',
|
Metadata = 'text',
|
||||||
Date = 'date'
|
Date = 'date'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_BROWSE_BY_TYPE = BrowseByType.Metadata;
|
export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata;
|
||||||
|
|
||||||
export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType) => GenericConstructor<any>>('getComponentByBrowseByType', {
|
export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType) => GenericConstructor<any>>('getComponentByBrowseByType', {
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -21,7 +21,7 @@ const map = new Map();
|
|||||||
* Decorator used for rendering Browse-By pages by type
|
* Decorator used for rendering Browse-By pages by type
|
||||||
* @param browseByType The type of page
|
* @param browseByType The type of page
|
||||||
*/
|
*/
|
||||||
export function rendersBrowseBy(browseByType: BrowseByType) {
|
export function rendersBrowseBy(browseByType: BrowseByDataType) {
|
||||||
return function decorator(component: any) {
|
return function decorator(component: any) {
|
||||||
if (hasNoValue(map.get(browseByType))) {
|
if (hasNoValue(map.get(browseByType))) {
|
||||||
map.set(browseByType, component);
|
map.set(browseByType, component);
|
||||||
|
@@ -2,20 +2,46 @@ import { BrowseBySwitcherComponent } from './browse-by-switcher.component';
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator';
|
||||||
import { environment } from '../../../environments/environment';
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
import { BehaviorSubject, of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
describe('BrowseBySwitcherComponent', () => {
|
describe('BrowseBySwitcherComponent', () => {
|
||||||
let comp: BrowseBySwitcherComponent;
|
let comp: BrowseBySwitcherComponent;
|
||||||
let fixture: ComponentFixture<BrowseBySwitcherComponent>;
|
let fixture: ComponentFixture<BrowseBySwitcherComponent>;
|
||||||
|
|
||||||
const types = environment.browseBy.types;
|
const types = [
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'title',
|
||||||
|
dataType: BrowseByDataType.Title,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'dateissued',
|
||||||
|
dataType: BrowseByDataType.Date,
|
||||||
|
metadataKeys: ['dc.date.issued']
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'author',
|
||||||
|
dataType: BrowseByDataType.Metadata,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'subject',
|
||||||
|
dataType: BrowseByDataType.Metadata,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
const params = new BehaviorSubject(createParamsWithId('initialValue'));
|
const data = new BehaviorSubject(createDataWithBrowseDefinition(new BrowseDefinition()));
|
||||||
|
|
||||||
const activatedRouteStub = {
|
const activatedRouteStub = {
|
||||||
params: params
|
data
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
@@ -34,20 +60,20 @@ describe('BrowseBySwitcherComponent', () => {
|
|||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
types.forEach((type) => {
|
types.forEach((type: BrowseDefinition) => {
|
||||||
describe(`when switching to a browse-by page for "${type.id}"`, () => {
|
describe(`when switching to a browse-by page for "${type.id}"`, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
params.next(createParamsWithId(type.id));
|
data.next(createDataWithBrowseDefinition(type));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should call getComponentByBrowseByType with type "${type.type}"`, () => {
|
it(`should call getComponentByBrowseByType with type "${type.dataType}"`, () => {
|
||||||
expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.type);
|
expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.dataType);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createParamsWithId(id) {
|
export function createDataWithBrowseDefinition(browseDefinition) {
|
||||||
return { id: id };
|
return { browseDefinition: browseDefinition };
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import { Component, Inject, OnInit } from '@angular/core';
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { BrowseByTypeConfig } from '../../../config/browse-by-type-config.interface';
|
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
||||||
import { environment } from '../../../environments/environment';
|
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-switcher',
|
selector: 'ds-browse-by-switcher',
|
||||||
@@ -26,15 +25,11 @@ export class BrowseBySwitcherComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the correct browse-by component by using the relevant config from environment.js
|
* Fetch the correct browse-by component by using the relevant config from the route data
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.browseByComponent = this.route.params.pipe(
|
this.browseByComponent = this.route.data.pipe(
|
||||||
map((params) => {
|
map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.dataType))
|
||||||
const id = params.id;
|
|
||||||
return environment.browseBy.types.find((config: BrowseByTypeConfig) => config.id === id);
|
|
||||||
}),
|
|
||||||
map((config: BrowseByTypeConfig) => this.getComponentByBrowseByType(config.type))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-
|
|||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { BrowseByType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
@@ -23,7 +23,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
|
|||||||
/**
|
/**
|
||||||
* Component for browsing items by title (dc.title)
|
* Component for browsing items by title (dc.title)
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByType.Title)
|
@rendersBrowseBy(BrowseByDataType.Title)
|
||||||
export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(protected route: ActivatedRoute,
|
||||||
|
@@ -9,9 +9,11 @@ describe(`BrowseDefinitionDataService`, () => {
|
|||||||
findAll: EMPTY,
|
findAll: EMPTY,
|
||||||
findByHref: EMPTY,
|
findByHref: EMPTY,
|
||||||
findAllByHref: EMPTY,
|
findAllByHref: EMPTY,
|
||||||
|
findById: EMPTY,
|
||||||
});
|
});
|
||||||
const hrefAll = 'https://rest.api/server/api/discover/browses';
|
const hrefAll = 'https://rest.api/server/api/discover/browses';
|
||||||
const hrefSingle = 'https://rest.api/server/api/discover/browses/author';
|
const hrefSingle = 'https://rest.api/server/api/discover/browses/author';
|
||||||
|
const id = 'author';
|
||||||
const options = new FindListOptions();
|
const options = new FindListOptions();
|
||||||
const linksToFollow = [
|
const linksToFollow = [
|
||||||
followLink('entries'),
|
followLink('entries'),
|
||||||
@@ -44,4 +46,10 @@ describe(`BrowseDefinitionDataService`, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe(`findById`, () => {
|
||||||
|
it(`should call findById on DataServiceImpl`, () => {
|
||||||
|
service.findAllByHref(id, options, true, false, ...linksToFollow);
|
||||||
|
expect(dataServiceImplSpy.findAllByHref).toHaveBeenCalledWith(id, options, true, false, ...linksToFollow);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -106,6 +106,21 @@ export class BrowseDefinitionDataService {
|
|||||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||||
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable of {@link RemoteData} of an object, based on its ID, with a list of
|
||||||
|
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
||||||
|
* @param id ID of object we want to retrieve
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||||
|
* {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
findById(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<BrowseDefinition>> {
|
||||||
|
return this.dataService.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
@@ -6,6 +6,7 @@ import { BROWSE_DEFINITION } from './browse-definition.resource-type';
|
|||||||
import { HALLink } from './hal-link.model';
|
import { HALLink } from './hal-link.model';
|
||||||
import { ResourceType } from './resource-type';
|
import { ResourceType } from './resource-type';
|
||||||
import { SortOption } from './sort-option.model';
|
import { SortOption } from './sort-option.model';
|
||||||
|
import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator';
|
||||||
|
|
||||||
@typedObject
|
@typedObject
|
||||||
export class BrowseDefinition extends CacheableObject {
|
export class BrowseDefinition extends CacheableObject {
|
||||||
@@ -33,6 +34,9 @@ export class BrowseDefinition extends CacheableObject {
|
|||||||
@autoserializeAs('metadata')
|
@autoserializeAs('metadata')
|
||||||
metadataKeys: string[];
|
metadataKeys: string[];
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
dataType: BrowseByDataType;
|
||||||
|
|
||||||
get self(): string {
|
get self(): string {
|
||||||
return this._links.self.href;
|
return this._links.self.href;
|
||||||
}
|
}
|
||||||
|
@@ -13,15 +13,48 @@ import { MenuService } from '../shared/menu/menu.service';
|
|||||||
import { MenuServiceStub } from '../shared/testing/menu-service.stub';
|
import { MenuServiceStub } from '../shared/testing/menu-service.stub';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { BrowseService } from '../core/browse/browse.service';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
|
import { buildPaginatedList } from '../core/data/paginated-list.model';
|
||||||
|
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||||
|
import { BrowseByDataType } from '../browse-by/browse-by-switcher/browse-by-decorator';
|
||||||
|
|
||||||
let comp: NavbarComponent;
|
let comp: NavbarComponent;
|
||||||
let fixture: ComponentFixture<NavbarComponent>;
|
let fixture: ComponentFixture<NavbarComponent>;
|
||||||
|
|
||||||
describe('NavbarComponent', () => {
|
describe('NavbarComponent', () => {
|
||||||
const menuService = new MenuServiceStub();
|
const menuService = new MenuServiceStub();
|
||||||
|
let browseDefinitions;
|
||||||
// waitForAsync beforeEach
|
// waitForAsync beforeEach
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
|
browseDefinitions = [
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'title',
|
||||||
|
dataType: BrowseByDataType.Title,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'dateissued',
|
||||||
|
dataType: BrowseByDataType.Date,
|
||||||
|
metadataKeys: ['dc.date.issued']
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'author',
|
||||||
|
dataType: BrowseByDataType.Metadata,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'subject',
|
||||||
|
dataType: BrowseByDataType.Metadata,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
@@ -33,7 +66,8 @@ describe('NavbarComponent', () => {
|
|||||||
Injector,
|
Injector,
|
||||||
{ provide: MenuService, useValue: menuService },
|
{ provide: MenuService, useValue: menuService },
|
||||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||||
{ provide: ActivatedRoute, useValue: {} }
|
{ provide: ActivatedRoute, useValue: {} },
|
||||||
|
{ provide: BrowseService, useValue: { getBrowseDefinitions: createSuccessfulRemoteDataObject$(buildPaginatedList(undefined, browseDefinitions)) } }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
|
@@ -6,7 +6,11 @@ import { MenuID, MenuItemType } from '../shared/menu/initial-menus-state';
|
|||||||
import { TextMenuItemModel } from '../shared/menu/menu-item/models/text.model';
|
import { TextMenuItemModel } from '../shared/menu/menu-item/models/text.model';
|
||||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { environment } from '../../environments/environment';
|
import { BrowseService } from '../core/browse/browse.service';
|
||||||
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
|
import { PaginatedList } from '../core/data/paginated-list.model';
|
||||||
|
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||||
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the public navbar
|
* Component representing the public navbar
|
||||||
@@ -26,7 +30,8 @@ export class NavbarComponent extends MenuComponent {
|
|||||||
|
|
||||||
constructor(protected menuService: MenuService,
|
constructor(protected menuService: MenuService,
|
||||||
protected injector: Injector,
|
protected injector: Injector,
|
||||||
public windowService: HostWindowService
|
public windowService: HostWindowService,
|
||||||
|
public browseService: BrowseService
|
||||||
) {
|
) {
|
||||||
super(menuService, injector);
|
super(menuService, injector);
|
||||||
}
|
}
|
||||||
@@ -52,37 +57,44 @@ export class NavbarComponent extends MenuComponent {
|
|||||||
text: `menu.section.browse_global_communities_and_collections`,
|
text: `menu.section.browse_global_communities_and_collections`,
|
||||||
link: `/community-list`
|
link: `/community-list`
|
||||||
} as LinkMenuItemModel
|
} as LinkMenuItemModel
|
||||||
},
|
}
|
||||||
/* News */
|
|
||||||
{
|
|
||||||
id: 'browse_global',
|
|
||||||
active: false,
|
|
||||||
visible: true,
|
|
||||||
index: 1,
|
|
||||||
model: {
|
|
||||||
type: MenuItemType.TEXT,
|
|
||||||
text: 'menu.section.browse_global'
|
|
||||||
} as TextMenuItemModel,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
// Read the different Browse-By types from config and add them to the browse menu
|
// Read the different Browse-By types from config and add them to the browse menu
|
||||||
const types = environment.browseBy.types;
|
this.browseService.getBrowseDefinitions()
|
||||||
types.forEach((typeConfig) => {
|
.pipe(getFirstCompletedRemoteData<PaginatedList<BrowseDefinition>>())
|
||||||
menuList.push({
|
.subscribe((browseDefListRD: RemoteData<PaginatedList<BrowseDefinition>>) => {
|
||||||
id: `browse_global_by_${typeConfig.id}`,
|
if (browseDefListRD.hasSucceeded) {
|
||||||
parentID: 'browse_global',
|
browseDefListRD.payload.page.forEach((browseDef: BrowseDefinition) => {
|
||||||
active: false,
|
menuList.push({
|
||||||
visible: true,
|
id: `browse_global_by_${browseDef.id}`,
|
||||||
model: {
|
parentID: 'browse_global',
|
||||||
type: MenuItemType.LINK,
|
active: false,
|
||||||
text: `menu.section.browse_global_by_${typeConfig.id}`,
|
visible: true,
|
||||||
link: `/browse/${typeConfig.id}`
|
model: {
|
||||||
} as LinkMenuItemModel
|
type: MenuItemType.LINK,
|
||||||
|
text: `menu.section.browse_global_by_${browseDef.id}`,
|
||||||
|
link: `/browse/${browseDef.id}`
|
||||||
|
} as LinkMenuItemModel
|
||||||
|
});
|
||||||
|
});
|
||||||
|
menuList.push(
|
||||||
|
/* Browse */
|
||||||
|
{
|
||||||
|
id: 'browse_global',
|
||||||
|
active: false,
|
||||||
|
visible: true,
|
||||||
|
index: 1,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.TEXT,
|
||||||
|
text: 'menu.section.browse_global'
|
||||||
|
} as TextMenuItemModel,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, {
|
||||||
|
shouldPersistOnRouteChange: true
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, {
|
|
||||||
shouldPersistOnRouteChange: true
|
|
||||||
})));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,10 +2,13 @@ import { Component, Input, OnInit } from '@angular/core';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||||
import { BrowseByTypeConfig } from '../../../config/browse-by-type-config.interface';
|
|
||||||
import { environment } from '../../../environments/environment';
|
|
||||||
import { getCommunityPageRoute } from '../../community-page/community-page-routing-paths';
|
import { getCommunityPageRoute } from '../../community-page/community-page-routing-paths';
|
||||||
import { getCollectionPageRoute } from '../../collection-page/collection-page-routing-paths';
|
import { getCollectionPageRoute } from '../../collection-page/collection-page-routing-paths';
|
||||||
|
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
|
|
||||||
export interface ComColPageNavOption {
|
export interface ComColPageNavOption {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -29,10 +32,6 @@ export class ComcolPageBrowseByComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() id: string;
|
@Input() id: string;
|
||||||
@Input() contentType: string;
|
@Input() contentType: string;
|
||||||
/**
|
|
||||||
* List of currently active browse configurations
|
|
||||||
*/
|
|
||||||
types: BrowseByTypeConfig[];
|
|
||||||
|
|
||||||
allOptions: ComColPageNavOption[];
|
allOptions: ComColPageNavOption[];
|
||||||
|
|
||||||
@@ -40,31 +39,39 @@ export class ComcolPageBrowseByComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router) {
|
private router: Router,
|
||||||
|
private browseService: BrowseService
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.allOptions = environment.browseBy.types
|
this.browseService.getBrowseDefinitions()
|
||||||
.map((config: BrowseByTypeConfig) => ({
|
.pipe(getFirstCompletedRemoteData<PaginatedList<BrowseDefinition>>())
|
||||||
id: config.id,
|
.subscribe((browseDefListRD: RemoteData<PaginatedList<BrowseDefinition>>) => {
|
||||||
label: `browse.comcol.by.${config.id}`,
|
if (browseDefListRD.hasSucceeded) {
|
||||||
routerLink: `/browse/${config.id}`,
|
this.allOptions = browseDefListRD.payload.page
|
||||||
params: { scope: this.id }
|
.map((config: BrowseDefinition) => ({
|
||||||
}));
|
id: config.id,
|
||||||
|
label: `browse.comcol.by.${config.id}`,
|
||||||
|
routerLink: `/browse/${config.id}`,
|
||||||
|
params: { scope: this.id }
|
||||||
|
}));
|
||||||
|
|
||||||
if (this.contentType === 'collection') {
|
if (this.contentType === 'collection') {
|
||||||
this.allOptions = [ {
|
this.allOptions = [{
|
||||||
id: this.id,
|
id: this.id,
|
||||||
label: 'collection.page.browse.recent.head',
|
label: 'collection.page.browse.recent.head',
|
||||||
routerLink: getCollectionPageRoute(this.id)
|
routerLink: getCollectionPageRoute(this.id)
|
||||||
}, ...this.allOptions ];
|
}, ...this.allOptions];
|
||||||
} else if (this.contentType === 'community') {
|
} else if (this.contentType === 'community') {
|
||||||
this.allOptions = [{
|
this.allOptions = [{
|
||||||
id: this.id,
|
id: this.id,
|
||||||
label: 'community.all-lists.head',
|
label: 'community.all-lists.head',
|
||||||
routerLink: getCommunityPageRoute(this.id)
|
routerLink: getCommunityPageRoute(this.id)
|
||||||
}, ...this.allOptions ];
|
}, ...this.allOptions];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.currentOptionId$ = this.route.params.pipe(
|
this.currentOptionId$ = this.route.params.pipe(
|
||||||
map((params: Params) => params.id)
|
map((params: Params) => params.id)
|
||||||
|
@@ -1,9 +1,21 @@
|
|||||||
<div>
|
<div>
|
||||||
<ds-form *ngIf="formModel"
|
<div class="modal-header">
|
||||||
#formRef="formComponent"
|
<h4 class="modal-title">{{'submission.sections.upload.edit.title' | translate}}</h4>
|
||||||
[formId]="formId"
|
<button type="button" class="close" (click)="onModalClose()" aria-label="Close" [disabled]="isSaving">
|
||||||
[formModel]="formModel"
|
<span aria-hidden="true">×</span>
|
||||||
[displaySubmit]="false"
|
</button>
|
||||||
[displayCancel]="false"
|
</div>
|
||||||
(dfChange)="onChange($event)"></ds-form>
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<ds-form *ngIf="formModel"
|
||||||
|
#formRef="formComponent"
|
||||||
|
[formId]="formId"
|
||||||
|
[formModel]="formModel"
|
||||||
|
[displaySubmit]="!isSaving"
|
||||||
|
[displayCancel]="!isSaving"
|
||||||
|
(submitForm)="onSubmit()"
|
||||||
|
(cancel)="onModalClose()"
|
||||||
|
(dfChange)="onChange($event)"></ds-form>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { waitForAsync, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
import { waitForAsync, ComponentFixture, inject, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -17,19 +17,37 @@ import { SubmissionService } from '../../../../submission.service';
|
|||||||
import { SubmissionSectionUploadFileEditComponent } from './section-upload-file-edit.component';
|
import { SubmissionSectionUploadFileEditComponent } from './section-upload-file-edit.component';
|
||||||
import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component';
|
import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component';
|
||||||
import {
|
import {
|
||||||
mockGroup,
|
|
||||||
mockSubmissionCollectionId,
|
mockSubmissionCollectionId,
|
||||||
mockSubmissionId,
|
mockSubmissionId,
|
||||||
mockUploadConfigResponse,
|
mockUploadConfigResponse,
|
||||||
mockUploadConfigResponseMetadata,
|
mockUploadConfigResponseMetadata,
|
||||||
mockUploadFiles
|
mockUploadFiles,
|
||||||
|
mockFileFormData,
|
||||||
|
mockSubmissionObject,
|
||||||
} from '../../../../../shared/mocks/submission.mock';
|
} from '../../../../../shared/mocks/submission.mock';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { FormComponent } from '../../../../../shared/form/form.component';
|
import { FormComponent } from '../../../../../shared/form/form.component';
|
||||||
import { FormService } from '../../../../../shared/form/form.service';
|
import { FormService } from '../../../../../shared/form/form.service';
|
||||||
import { getMockFormService } from '../../../../../shared/mocks/form-service.mock';
|
import { getMockFormService } from '../../../../../shared/mocks/form-service.mock';
|
||||||
import { Group } from '../../../../../core/eperson/models/group.model';
|
|
||||||
import { createTestComponent } from '../../../../../shared/testing/utils.test';
|
import { createTestComponent } from '../../../../../shared/testing/utils.test';
|
||||||
|
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder';
|
||||||
|
import { SubmissionJsonPatchOperationsServiceStub } from '../../../../../shared/testing/submission-json-patch-operations-service.stub';
|
||||||
|
import { SubmissionJsonPatchOperationsService } from '../../../../../core/submission/submission-json-patch-operations.service';
|
||||||
|
import { SectionUploadService } from '../../section-upload.service';
|
||||||
|
import { getMockSectionUploadService } from '../../../../../shared/mocks/section-upload.service.mock';
|
||||||
|
import { FormFieldMetadataValueObject } from '../../../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||||
|
import { JsonPatchOperationPathCombiner } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
|
import { dateToISOFormat } from '../../../../../shared/date.util';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
|
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
|
||||||
|
add: jasmine.createSpy('add'),
|
||||||
|
replace: jasmine.createSpy('replace'),
|
||||||
|
remove: jasmine.createSpy('remove'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const formMetadataMock = ['dc.title', 'dc.description'];
|
||||||
|
|
||||||
describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
||||||
|
|
||||||
@@ -38,7 +56,12 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
let fixture: ComponentFixture<SubmissionSectionUploadFileEditComponent>;
|
let fixture: ComponentFixture<SubmissionSectionUploadFileEditComponent>;
|
||||||
let submissionServiceStub: SubmissionServiceStub;
|
let submissionServiceStub: SubmissionServiceStub;
|
||||||
let formbuilderService: any;
|
let formbuilderService: any;
|
||||||
|
let operationsBuilder: any;
|
||||||
|
let operationsService: any;
|
||||||
|
let formService: any;
|
||||||
|
let uploadService: any;
|
||||||
|
|
||||||
|
const submissionJsonPatchOperationsServiceStub = new SubmissionJsonPatchOperationsServiceStub();
|
||||||
const submissionId = mockSubmissionId;
|
const submissionId = mockSubmissionId;
|
||||||
const sectionId = 'upload';
|
const sectionId = 'upload';
|
||||||
const collectionId = mockSubmissionCollectionId;
|
const collectionId = mockSubmissionCollectionId;
|
||||||
@@ -48,6 +71,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
const fileIndex = '0';
|
const fileIndex = '0';
|
||||||
const fileId = '123456-test-upload';
|
const fileId = '123456-test-upload';
|
||||||
const fileData: any = mockUploadFiles[0];
|
const fileData: any = mockUploadFiles[0];
|
||||||
|
const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId, 'files', fileIndex);
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -66,9 +90,15 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: FormService, useValue: getMockFormService() },
|
{ provide: FormService, useValue: getMockFormService() },
|
||||||
{ provide: SubmissionService, useClass: SubmissionServiceStub },
|
{ provide: SubmissionService, useClass: SubmissionServiceStub },
|
||||||
|
{ provide: SubmissionJsonPatchOperationsService, useValue: submissionJsonPatchOperationsServiceStub },
|
||||||
|
{ provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder },
|
||||||
|
{ provide: SectionUploadService, useValue: getMockSectionUploadService() },
|
||||||
FormBuilderService,
|
FormBuilderService,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
SubmissionSectionUploadFileEditComponent
|
SubmissionSectionUploadFileEditComponent,
|
||||||
|
NgbModal,
|
||||||
|
NgbActiveModal,
|
||||||
|
FormComponent,
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents().then();
|
}).compileComponents().then();
|
||||||
@@ -114,6 +144,10 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
compAsAny = comp;
|
compAsAny = comp;
|
||||||
submissionServiceStub = TestBed.inject(SubmissionService as any);
|
submissionServiceStub = TestBed.inject(SubmissionService as any);
|
||||||
formbuilderService = TestBed.inject(FormBuilderService);
|
formbuilderService = TestBed.inject(FormBuilderService);
|
||||||
|
operationsBuilder = TestBed.inject(JsonPatchOperationsBuilder);
|
||||||
|
operationsService = TestBed.inject(SubmissionJsonPatchOperationsService);
|
||||||
|
formService = TestBed.inject(FormService);
|
||||||
|
uploadService = TestBed.inject(SectionUploadService);
|
||||||
|
|
||||||
comp.submissionId = submissionId;
|
comp.submissionId = submissionId;
|
||||||
comp.collectionId = collectionId;
|
comp.collectionId = collectionId;
|
||||||
@@ -123,6 +157,9 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
comp.fileIndex = fileIndex;
|
comp.fileIndex = fileIndex;
|
||||||
comp.fileId = fileId;
|
comp.fileId = fileId;
|
||||||
comp.configMetadataForm = configMetadataForm;
|
comp.configMetadataForm = configMetadataForm;
|
||||||
|
comp.formMetadata = formMetadataMock;
|
||||||
|
|
||||||
|
formService.isValid.and.returnValue(of(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -135,7 +172,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
comp.fileData = fileData;
|
comp.fileData = fileData;
|
||||||
comp.formId = 'testFileForm';
|
comp.formId = 'testFileForm';
|
||||||
|
|
||||||
comp.ngOnChanges();
|
comp.ngOnInit();
|
||||||
|
|
||||||
expect(comp.formModel).toBeDefined();
|
expect(comp.formModel).toBeDefined();
|
||||||
expect(comp.formModel.length).toBe(2);
|
expect(comp.formModel.length).toBe(2);
|
||||||
@@ -165,7 +202,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
comp.fileData = fileData;
|
comp.fileData = fileData;
|
||||||
comp.formId = 'testFileForm';
|
comp.formId = 'testFileForm';
|
||||||
|
|
||||||
comp.ngOnChanges();
|
comp.ngOnInit();
|
||||||
|
|
||||||
const model: DynamicSelectModel<string> = formbuilderService.findById('name', comp.formModel, 0);
|
const model: DynamicSelectModel<string> = formbuilderService.findById('name', comp.formModel, 0);
|
||||||
const formGroup = formbuilderService.createFormGroup(comp.formModel);
|
const formGroup = formbuilderService.createFormGroup(comp.formModel);
|
||||||
@@ -186,6 +223,82 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
comp.setOptions(model, control);
|
comp.setOptions(model, control);
|
||||||
expect(formbuilderService.findById).toHaveBeenCalledWith('startDate', (model.parent as DynamicFormArrayGroupModel).group);
|
expect(formbuilderService.findById).toHaveBeenCalledWith('startDate', (model.parent as DynamicFormArrayGroupModel).group);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should retrieve Value From Field properly', () => {
|
||||||
|
let field;
|
||||||
|
expect(compAsAny.retrieveValueFromField(field)).toBeUndefined();
|
||||||
|
|
||||||
|
field = new FormFieldMetadataValueObject('test');
|
||||||
|
expect(compAsAny.retrieveValueFromField(field)).toBe('test');
|
||||||
|
|
||||||
|
field = [new FormFieldMetadataValueObject('test')];
|
||||||
|
expect(compAsAny.retrieveValueFromField(field)).toBe('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save Bitstream File data properly when form is valid', fakeAsync(() => {
|
||||||
|
compAsAny.formRef = {formGroup: null};
|
||||||
|
compAsAny.fileData = fileData;
|
||||||
|
compAsAny.pathCombiner = pathCombiner;
|
||||||
|
formService.validateAllFormFields.and.callFake(() => null);
|
||||||
|
formService.isValid.and.returnValue(of(true));
|
||||||
|
formService.getFormData.and.returnValue(of(mockFileFormData));
|
||||||
|
|
||||||
|
const response = [
|
||||||
|
Object.assign(mockSubmissionObject, {
|
||||||
|
sections: {
|
||||||
|
upload: {
|
||||||
|
files: mockUploadFiles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
operationsService.jsonPatchByResourceID.and.returnValue(of(response));
|
||||||
|
|
||||||
|
const accessConditionsToSave = [
|
||||||
|
{ name: 'openaccess' },
|
||||||
|
{ name: 'lease', endDate: dateToISOFormat('2019-01-16T00:00:00Z') },
|
||||||
|
{ name: 'embargo', startDate: dateToISOFormat('2019-01-16T00:00:00Z') },
|
||||||
|
];
|
||||||
|
comp.saveBitstreamData();
|
||||||
|
tick();
|
||||||
|
|
||||||
|
let path = 'metadata/dc.title';
|
||||||
|
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
||||||
|
pathCombiner.getPath(path),
|
||||||
|
mockFileFormData.metadata['dc.title'],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
path = 'metadata/dc.description';
|
||||||
|
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
||||||
|
pathCombiner.getPath(path),
|
||||||
|
mockFileFormData.metadata['dc.description'],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
path = 'accessConditions';
|
||||||
|
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
||||||
|
pathCombiner.getPath(path),
|
||||||
|
accessConditionsToSave,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(uploadService.updateFileData).toHaveBeenCalledWith(submissionId, sectionId, mockUploadFiles[0].uuid, mockUploadFiles[0]);
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => {
|
||||||
|
compAsAny.formRef = {formGroup: null};
|
||||||
|
compAsAny.pathCombiner = pathCombiner;
|
||||||
|
formService.validateAllFormFields.and.callFake(() => null);
|
||||||
|
formService.isValid.and.returnValue(of(false));
|
||||||
|
comp.saveBitstreamData();
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(uploadService.updateFileData).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectorRef, Component, Input, OnChanges, ViewChild } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -32,13 +32,23 @@ import {
|
|||||||
BITSTREAM_METADATA_FORM_GROUP_LAYOUT
|
BITSTREAM_METADATA_FORM_GROUP_LAYOUT
|
||||||
} from './section-upload-file-edit.model';
|
} from './section-upload-file-edit.model';
|
||||||
import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component';
|
import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component';
|
||||||
import { isNotEmpty } from '../../../../../shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty, isNotNull } from '../../../../../shared/empty.util';
|
||||||
import { SubmissionFormsModel } from '../../../../../core/config/models/config-submission-forms.model';
|
import { SubmissionFormsModel } from '../../../../../core/config/models/config-submission-forms.model';
|
||||||
import { FormFieldModel } from '../../../../../shared/form/builder/models/form-field.model';
|
import { FormFieldModel } from '../../../../../shared/form/builder/models/form-field.model';
|
||||||
import { AccessConditionOption } from '../../../../../core/config/models/config-access-condition-option.model';
|
import { AccessConditionOption } from '../../../../../core/config/models/config-access-condition-option.model';
|
||||||
import { SubmissionService } from '../../../../submission.service';
|
import { SubmissionService } from '../../../../submission.service';
|
||||||
import { FormService } from '../../../../../shared/form/form.service';
|
import { FormService } from '../../../../../shared/form/form.service';
|
||||||
import { FormComponent } from '../../../../../shared/form/form.component';
|
import { FormComponent } from '../../../../../shared/form/form.component';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { filter, mergeMap, take } from 'rxjs/operators';
|
||||||
|
import { dateToISOFormat } from '../../../../../shared/date.util';
|
||||||
|
import { SubmissionObject } from '../../../../../core/submission/models/submission-object.model';
|
||||||
|
import { WorkspaceitemSectionUploadObject } from '../../../../../core/submission/models/workspaceitem-section-upload.model';
|
||||||
|
import { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder';
|
||||||
|
import { SubmissionJsonPatchOperationsService } from '../../../../../core/submission/submission-json-patch-operations.service';
|
||||||
|
import { JsonPatchOperationPathCombiner } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
|
import { SectionUploadService } from '../../section-upload.service';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component represents the edit form for bitstream
|
* This component represents the edit form for bitstream
|
||||||
@@ -48,105 +58,246 @@ import { FormComponent } from '../../../../../shared/form/form.component';
|
|||||||
styleUrls: ['./section-upload-file-edit.component.scss'],
|
styleUrls: ['./section-upload-file-edit.component.scss'],
|
||||||
templateUrl: './section-upload-file-edit.component.html',
|
templateUrl: './section-upload-file-edit.component.html',
|
||||||
})
|
})
|
||||||
export class SubmissionSectionUploadFileEditComponent implements OnChanges {
|
export class SubmissionSectionUploadFileEditComponent implements OnInit {
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of available access condition
|
|
||||||
* @type {Array}
|
|
||||||
*/
|
|
||||||
@Input() availableAccessConditionOptions: any[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The submission id
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
@Input() collectionId: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define if collection access conditions policy type :
|
|
||||||
* POLICY_DEFAULT_NO_LIST : is not possible to define additional access group/s for the single file
|
|
||||||
* POLICY_DEFAULT_WITH_LIST : is possible to define additional access group/s for the single file
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
@Input() collectionPolicyType: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The configuration for the bitstream's metadata form
|
|
||||||
* @type {SubmissionFormsModel}
|
|
||||||
*/
|
|
||||||
@Input() configMetadataForm: SubmissionFormsModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The bitstream's metadata data
|
|
||||||
* @type {WorkspaceitemSectionUploadFileObject}
|
|
||||||
*/
|
|
||||||
@Input() fileData: WorkspaceitemSectionUploadFileObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The bitstream id
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
@Input() fileId: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The bitstream array key
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
@Input() fileIndex: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The form id
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
@Input() formId: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The section id
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
@Input() sectionId: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The submission id
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
@Input() submissionId: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The form model
|
|
||||||
* @type {DynamicFormControlModel[]}
|
|
||||||
*/
|
|
||||||
public formModel: DynamicFormControlModel[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The FormComponent reference
|
* The FormComponent reference
|
||||||
*/
|
*/
|
||||||
@ViewChild('formRef') public formRef: FormComponent;
|
@ViewChild('formRef') public formRef: FormComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of available access condition
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
public availableAccessConditionOptions: any[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The submission id
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public collectionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define if collection access conditions policy type :
|
||||||
|
* POLICY_DEFAULT_NO_LIST : is not possible to define additional access group/s for the single file
|
||||||
|
* POLICY_DEFAULT_WITH_LIST : is possible to define additional access group/s for the single file
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
public collectionPolicyType: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration for the bitstream's metadata form
|
||||||
|
* @type {SubmissionFormsModel}
|
||||||
|
*/
|
||||||
|
public configMetadataForm: SubmissionFormsModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bitstream's metadata data
|
||||||
|
* @type {WorkspaceitemSectionUploadFileObject}
|
||||||
|
*/
|
||||||
|
public fileData: WorkspaceitemSectionUploadFileObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bitstream id
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public fileId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bitstream array key
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public fileIndex: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form id
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public formId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The section id
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public sectionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The submission id
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public submissionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of all available metadata
|
||||||
|
*/
|
||||||
|
formMetadata: string[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form model
|
||||||
|
* @type {DynamicFormControlModel[]}
|
||||||
|
*/
|
||||||
|
formModel: DynamicFormControlModel[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When `true` form controls are deactivated
|
||||||
|
*/
|
||||||
|
isSaving = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [JsonPatchOperationPathCombiner] object
|
||||||
|
* @type {JsonPatchOperationPathCombiner}
|
||||||
|
*/
|
||||||
|
protected pathCombiner: JsonPatchOperationPathCombiner;
|
||||||
|
|
||||||
|
protected subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize instance variables
|
* Initialize instance variables
|
||||||
*
|
*
|
||||||
|
* @param activeModal
|
||||||
* @param {ChangeDetectorRef} cdr
|
* @param {ChangeDetectorRef} cdr
|
||||||
* @param {FormBuilderService} formBuilderService
|
* @param {FormBuilderService} formBuilderService
|
||||||
* @param {FormService} formService
|
* @param {FormService} formService
|
||||||
* @param {SubmissionService} submissionService
|
* @param {SubmissionService} submissionService
|
||||||
|
* @param {JsonPatchOperationsBuilder} operationsBuilder
|
||||||
|
* @param {SubmissionJsonPatchOperationsService} operationsService
|
||||||
|
* @param {SectionUploadService} uploadService
|
||||||
*/
|
*/
|
||||||
constructor(private cdr: ChangeDetectorRef,
|
constructor(
|
||||||
private formBuilderService: FormBuilderService,
|
protected activeModal: NgbActiveModal,
|
||||||
private formService: FormService,
|
private cdr: ChangeDetectorRef,
|
||||||
private submissionService: SubmissionService) {
|
private formBuilderService: FormBuilderService,
|
||||||
|
private formService: FormService,
|
||||||
|
private submissionService: SubmissionService,
|
||||||
|
private operationsBuilder: JsonPatchOperationsBuilder,
|
||||||
|
private operationsService: SubmissionJsonPatchOperationsService,
|
||||||
|
private uploadService: SectionUploadService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize form model values
|
||||||
|
*
|
||||||
|
* @param formModel
|
||||||
|
* The form model
|
||||||
|
*/
|
||||||
|
public initModelData(formModel: DynamicFormControlModel[]) {
|
||||||
|
this.fileData.accessConditions.forEach((accessCondition, index) => {
|
||||||
|
Array.of('name', 'startDate', 'endDate')
|
||||||
|
.filter((key) => accessCondition.hasOwnProperty(key) && isNotEmpty(accessCondition[key]))
|
||||||
|
.forEach((key) => {
|
||||||
|
const metadataModel: any = this.formBuilderService.findById(key, formModel, index);
|
||||||
|
if (metadataModel) {
|
||||||
|
if (metadataModel.type === DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER) {
|
||||||
|
const date = new Date(accessCondition[key]);
|
||||||
|
metadataModel.value = {
|
||||||
|
year: date.getUTCFullYear(),
|
||||||
|
month: date.getUTCMonth() + 1,
|
||||||
|
day: date.getUTCDate()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
metadataModel.value = accessCondition[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch form model update when changing an access condition
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* The event emitted
|
||||||
|
*/
|
||||||
|
onChange(event: DynamicFormControlEvent) {
|
||||||
|
if (event.model.id === 'name') {
|
||||||
|
this.setOptions(event.model, event.control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onModalClose() {
|
||||||
|
this.activeModal.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
this.isSaving = true;
|
||||||
|
this.saveBitstreamData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update `startDate`, 'groupUUID' and 'endDate' model
|
||||||
|
*
|
||||||
|
* @param model
|
||||||
|
* The [[DynamicFormControlModel]] object
|
||||||
|
* @param control
|
||||||
|
* The [[FormControl]] object
|
||||||
|
*/
|
||||||
|
public setOptions(model: DynamicFormControlModel, control: FormControl) {
|
||||||
|
let accessCondition: AccessConditionOption = null;
|
||||||
|
this.availableAccessConditionOptions.filter((element) => element.name === control.value)
|
||||||
|
.forEach((element) => accessCondition = element );
|
||||||
|
if (isNotEmpty(accessCondition)) {
|
||||||
|
const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true;
|
||||||
|
|
||||||
|
const startDateControl: FormControl = control.parent.get('startDate') as FormControl;
|
||||||
|
const endDateControl: FormControl = control.parent.get('endDate') as FormControl;
|
||||||
|
|
||||||
|
// Clear previous state
|
||||||
|
startDateControl?.markAsUntouched();
|
||||||
|
endDateControl?.markAsUntouched();
|
||||||
|
|
||||||
|
startDateControl?.setValue(null);
|
||||||
|
control.parent.markAsDirty();
|
||||||
|
endDateControl?.setValue(null);
|
||||||
|
|
||||||
|
if (showGroups) {
|
||||||
|
if (accessCondition.hasStartDate) {
|
||||||
|
const startDateModel = this.formBuilderService.findById(
|
||||||
|
'startDate',
|
||||||
|
(model.parent as DynamicFormArrayGroupModel).group) as DynamicDateControlModel;
|
||||||
|
|
||||||
|
const min = new Date(accessCondition.maxStartDate);
|
||||||
|
startDateModel.max = {
|
||||||
|
year: min.getUTCFullYear(),
|
||||||
|
month: min.getUTCMonth() + 1,
|
||||||
|
day: min.getUTCDate()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (accessCondition.hasEndDate) {
|
||||||
|
const endDateModel = this.formBuilderService.findById(
|
||||||
|
'endDate',
|
||||||
|
(model.parent as DynamicFormArrayGroupModel).group) as DynamicDateControlModel;
|
||||||
|
|
||||||
|
const max = new Date(accessCondition.maxEndDate);
|
||||||
|
endDateModel.max = {
|
||||||
|
year: max.getUTCFullYear(),
|
||||||
|
month: max.getUTCMonth() + 1,
|
||||||
|
day: max.getUTCDate()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch form model init
|
* Dispatch form model init
|
||||||
*/
|
*/
|
||||||
ngOnChanges() {
|
ngOnInit() {
|
||||||
if (this.fileData && this.formId) {
|
if (this.fileData && this.formId) {
|
||||||
this.formModel = this.buildFileEditForm();
|
this.formModel = this.buildFileEditForm();
|
||||||
this.cdr.detectChanges();
|
this.cdr.detectChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.unsubscribeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected retrieveValueFromField(field: any) {
|
||||||
|
const temp = Array.isArray(field) ? field[0] : field;
|
||||||
|
return (temp) ? temp.value : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize form model
|
* Initialize form model
|
||||||
*/
|
*/
|
||||||
@@ -193,17 +344,17 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges {
|
|||||||
const showEnd: boolean = condition.hasEndDate === true;
|
const showEnd: boolean = condition.hasEndDate === true;
|
||||||
const showGroups: boolean = showStart || showEnd;
|
const showGroups: boolean = showStart || showEnd;
|
||||||
if (showStart) {
|
if (showStart) {
|
||||||
hasStart.push({ id: 'name', value: condition.name });
|
hasStart.push({id: 'name', value: condition.name});
|
||||||
}
|
}
|
||||||
if (showEnd) {
|
if (showEnd) {
|
||||||
hasEnd.push({ id: 'name', value: condition.name });
|
hasEnd.push({id: 'name', value: condition.name});
|
||||||
}
|
}
|
||||||
if (showGroups) {
|
if (showGroups) {
|
||||||
hasGroups.push({ id: 'name', value: condition.name });
|
hasGroups.push({id: 'name', value: condition.name});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const confStart = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasStart }] };
|
const confStart = {relations: [{match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasStart}]};
|
||||||
const confEnd = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasEnd }] };
|
const confEnd = {relations: [{match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasEnd}]};
|
||||||
|
|
||||||
accessConditionsArrayConfig.groupFactory = () => {
|
accessConditionsArrayConfig.groupFactory = () => {
|
||||||
const type = new DynamicSelectModel(accessConditionTypeModelConfig, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT);
|
const type = new DynamicSelectModel(accessConditionTypeModelConfig, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT);
|
||||||
@@ -213,7 +364,9 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges {
|
|||||||
const startDate = new DynamicDatePickerModel(startDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT);
|
const startDate = new DynamicDatePickerModel(startDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT);
|
||||||
const endDate = new DynamicDatePickerModel(endDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT);
|
const endDate = new DynamicDatePickerModel(endDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT);
|
||||||
const accessConditionGroupConfig = Object.assign({}, BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG);
|
const accessConditionGroupConfig = Object.assign({}, BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG);
|
||||||
accessConditionGroupConfig.group = [type, startDate, endDate];
|
accessConditionGroupConfig.group = [type];
|
||||||
|
if (hasStart.length > 0) { accessConditionGroupConfig.group.push(startDate); }
|
||||||
|
if (hasEnd.length > 0) { accessConditionGroupConfig.group.push(endDate); }
|
||||||
return [new DynamicFormGroupModel(accessConditionGroupConfig, BITSTREAM_ACCESS_CONDITION_GROUP_LAYOUT)];
|
return [new DynamicFormGroupModel(accessConditionGroupConfig, BITSTREAM_ACCESS_CONDITION_GROUP_LAYOUT)];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -229,98 +382,95 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize form model values
|
* Save bitstream metadata
|
||||||
*
|
|
||||||
* @param formModel
|
|
||||||
* The form model
|
|
||||||
*/
|
*/
|
||||||
public initModelData(formModel: DynamicFormControlModel[]) {
|
saveBitstreamData() {
|
||||||
this.fileData.accessConditions.forEach((accessCondition, index) => {
|
// validate form
|
||||||
Array.of('name', 'startDate', 'endDate')
|
this.formService.validateAllFormFields(this.formRef.formGroup);
|
||||||
.filter((key) => accessCondition.hasOwnProperty(key) && isNotEmpty(accessCondition[key]))
|
const saveBitstreamDataSubscription = this.formService.isValid(this.formId).pipe(
|
||||||
.forEach((key) => {
|
take(1),
|
||||||
const metadataModel: any = this.formBuilderService.findById(key, formModel, index);
|
filter((isValid) => isValid),
|
||||||
if (metadataModel) {
|
mergeMap(() => this.formService.getFormData(this.formId)),
|
||||||
if (metadataModel.type === DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER) {
|
take(1),
|
||||||
const date = new Date(accessCondition[key]);
|
mergeMap((formData: any) => {
|
||||||
metadataModel.value = {
|
// collect bitstream metadata
|
||||||
year: date.getUTCFullYear(),
|
Object.keys((formData.metadata))
|
||||||
month: date.getUTCMonth() + 1,
|
.filter((key) => isNotEmpty(formData.metadata[key]))
|
||||||
day: date.getUTCDate()
|
.forEach((key) => {
|
||||||
};
|
const metadataKey = key.replace(/_/g, '.');
|
||||||
} else {
|
const path = `metadata/${metadataKey}`;
|
||||||
metadataModel.value = accessCondition[key];
|
this.operationsBuilder.add(this.pathCombiner.getPath(path), formData.metadata[key], true);
|
||||||
|
});
|
||||||
|
Object.keys((this.fileData.metadata))
|
||||||
|
.filter((key) => isNotEmpty(this.fileData.metadata[key]))
|
||||||
|
.filter((key) => hasNoValue(formData.metadata[key]))
|
||||||
|
.filter((key) => this.formMetadata.includes(key))
|
||||||
|
.forEach((key) => {
|
||||||
|
const metadataKey = key.replace(/_/g, '.');
|
||||||
|
const path = `metadata/${metadataKey}`;
|
||||||
|
this.operationsBuilder.remove(this.pathCombiner.getPath(path));
|
||||||
|
});
|
||||||
|
const accessConditionsToSave = [];
|
||||||
|
formData.accessConditions
|
||||||
|
.map((accessConditions) => accessConditions.accessConditionGroup)
|
||||||
|
.filter((accessCondition) => isNotEmpty(accessCondition))
|
||||||
|
.forEach((accessCondition) => {
|
||||||
|
let accessConditionOpt;
|
||||||
|
|
||||||
|
this.availableAccessConditionOptions
|
||||||
|
.filter((element) => isNotNull(accessCondition.name) && element.name === accessCondition.name[0].value)
|
||||||
|
.forEach((element) => accessConditionOpt = element);
|
||||||
|
|
||||||
|
if (accessConditionOpt) {
|
||||||
|
const currentAccessCondition = Object.assign({}, accessCondition);
|
||||||
|
currentAccessCondition.name = this.retrieveValueFromField(accessCondition.name);
|
||||||
|
|
||||||
|
/* When start and end date fields are deactivated, their values may be still present in formData,
|
||||||
|
therefore it is necessary to delete them if they're not allowed by the current access condition option. */
|
||||||
|
if (!accessConditionOpt.hasStartDate) {
|
||||||
|
delete currentAccessCondition.startDate;
|
||||||
|
} else if (accessCondition.startDate) {
|
||||||
|
const startDate = this.retrieveValueFromField(accessCondition.startDate);
|
||||||
|
currentAccessCondition.startDate = dateToISOFormat(startDate);
|
||||||
|
}
|
||||||
|
if (!accessConditionOpt.hasEndDate) {
|
||||||
|
delete currentAccessCondition.endDate;
|
||||||
|
} else if (accessCondition.endDate) {
|
||||||
|
const endDate = this.retrieveValueFromField(accessCondition.endDate);
|
||||||
|
currentAccessCondition.endDate = dateToISOFormat(endDate);
|
||||||
|
}
|
||||||
|
accessConditionsToSave.push(currentAccessCondition);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (isNotEmpty(accessConditionsToSave)) {
|
||||||
* Dispatch form model update when changing an access condition
|
this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true);
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* The event emitted
|
|
||||||
*/
|
|
||||||
public onChange(event: DynamicFormControlEvent) {
|
|
||||||
if (event.model.id === 'name') {
|
|
||||||
this.setOptions(event.model, event.control);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update `startDate`, 'groupUUID' and 'endDate' model
|
|
||||||
*
|
|
||||||
* @param model
|
|
||||||
* The [[DynamicFormControlModel]] object
|
|
||||||
* @param control
|
|
||||||
* The [[FormControl]] object
|
|
||||||
*/
|
|
||||||
public setOptions(model: DynamicFormControlModel, control: FormControl) {
|
|
||||||
let accessCondition: AccessConditionOption = null;
|
|
||||||
this.availableAccessConditionOptions.filter((element) => element.name === control.value)
|
|
||||||
.forEach((element) => accessCondition = element);
|
|
||||||
if (isNotEmpty(accessCondition)) {
|
|
||||||
const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true;
|
|
||||||
|
|
||||||
const startDateControl: FormControl = control.parent.get('startDate') as FormControl;
|
|
||||||
const endDateControl: FormControl = control.parent.get('endDate') as FormControl;
|
|
||||||
|
|
||||||
// Clear previous state
|
|
||||||
startDateControl.markAsUntouched();
|
|
||||||
endDateControl.markAsUntouched();
|
|
||||||
|
|
||||||
startDateControl.setValue(null);
|
|
||||||
control.parent.markAsDirty();
|
|
||||||
endDateControl.setValue(null);
|
|
||||||
|
|
||||||
if (showGroups) {
|
|
||||||
if (accessCondition.hasStartDate) {
|
|
||||||
const startDateModel = this.formBuilderService.findById(
|
|
||||||
'startDate',
|
|
||||||
(model.parent as DynamicFormArrayGroupModel).group) as DynamicDateControlModel;
|
|
||||||
|
|
||||||
const min = new Date(accessCondition.maxStartDate);
|
|
||||||
startDateModel.max = {
|
|
||||||
year: min.getUTCFullYear(),
|
|
||||||
month: min.getUTCMonth() + 1,
|
|
||||||
day: min.getUTCDate()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
if (accessCondition.hasEndDate) {
|
|
||||||
const endDateModel = this.formBuilderService.findById(
|
|
||||||
'endDate',
|
|
||||||
(model.parent as DynamicFormArrayGroupModel).group) as DynamicDateControlModel;
|
|
||||||
|
|
||||||
const max = new Date(accessCondition.maxEndDate);
|
// dispatch a PATCH request to save metadata
|
||||||
endDateModel.max = {
|
return this.operationsService.jsonPatchByResourceID(
|
||||||
year: max.getUTCFullYear(),
|
this.submissionService.getSubmissionObjectLinkName(),
|
||||||
month: max.getUTCMonth() + 1,
|
this.submissionId,
|
||||||
day: max.getUTCDate()
|
this.pathCombiner.rootElement,
|
||||||
};
|
this.pathCombiner.subRootElement);
|
||||||
}
|
})
|
||||||
|
).subscribe((result: SubmissionObject[]) => {
|
||||||
|
if (result[0].sections[this.sectionId]) {
|
||||||
|
const uploadSection = (result[0].sections[this.sectionId] as WorkspaceitemSectionUploadObject);
|
||||||
|
Object.keys(uploadSection.files)
|
||||||
|
.filter((key) => uploadSection.files[key].uuid === this.fileId)
|
||||||
|
.forEach((key) => this.uploadService.updateFileData(
|
||||||
|
this.submissionId, this.sectionId, this.fileId, uploadSection.files[key])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
this.isSaving = false;
|
||||||
|
this.activeModal.close();
|
||||||
|
});
|
||||||
|
this.subscriptions.push(saveBitstreamDataSubscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsubscribeAll() {
|
||||||
|
this.subscriptions.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,15 +8,15 @@
|
|||||||
<div class="float-left w-75">
|
<div class="float-left w-75">
|
||||||
<h3>{{fileName}} <span class="text-muted">({{fileData?.sizeBytes | dsFileSize}})</span></h3>
|
<h3>{{fileName}} <span class="text-muted">({{fileData?.sizeBytes | dsFileSize}})</span></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="float-right w-15" [class.sticky-buttons]="!readMode">
|
<div class="float-right w-15">
|
||||||
<ng-container *ngIf="readMode">
|
<ng-container>
|
||||||
<ds-file-download-link [cssClasses]="'btn btn-link-focus'" [isBlank]="true" [bitstream]="getBitstream()" [enableRequestACopy]="false">
|
<ds-file-download-link [cssClasses]="'btn btn-link-focus'" [isBlank]="true" [bitstream]="getBitstream()" [enableRequestACopy]="false">
|
||||||
<i class="fa fa-download fa-2x text-normal" aria-hidden="true"></i>
|
<i class="fa fa-download fa-2x text-normal" aria-hidden="true"></i>
|
||||||
</ds-file-download-link>
|
</ds-file-download-link>
|
||||||
<button class="btn btn-link-focus"
|
<button class="btn btn-link-focus"
|
||||||
[attr.aria-label]="'submission.sections.upload.edit.title' | translate"
|
[attr.aria-label]="'submission.sections.upload.edit.title' | translate"
|
||||||
title="{{ 'submission.sections.upload.edit.title' | translate }}"
|
title="{{ 'submission.sections.upload.edit.title' | translate }}"
|
||||||
(click)="$event.preventDefault();switchMode();">
|
(click)="$event.preventDefault();editBitstreamData();">
|
||||||
<i class="fa fa-edit fa-2x text-normal"></i>
|
<i class="fa fa-edit fa-2x text-normal"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-link-focus"
|
<button class="btn btn-link-focus"
|
||||||
@@ -28,40 +28,9 @@
|
|||||||
<i *ngIf="!(processingDelete$ | async)" class="fa fa-trash fa-2x text-danger"></i>
|
<i *ngIf="!(processingDelete$ | async)" class="fa fa-trash fa-2x text-danger"></i>
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!readMode">
|
|
||||||
<button class="btn btn-link-focus"
|
|
||||||
[attr.aria-label]="'submission.sections.upload.save-metadata' | translate"
|
|
||||||
title="{{ 'submission.sections.upload.save-metadata' | translate }}"
|
|
||||||
(click)="saveBitstreamData($event);">
|
|
||||||
<i class="fa fa-save fa-2x text-success"></i>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-link-focus"
|
|
||||||
[attr.aria-label]="'submission.sections.upload.undo' | translate"
|
|
||||||
title="{{ 'submission.sections.upload.undo' | translate }}"
|
|
||||||
(click)="$event.preventDefault();switchMode();"><i class="fa fa-ban fa-2x text-warning"></i></button>
|
|
||||||
<button class="btn btn-link-focus"
|
|
||||||
[attr.aria-label]="'submission.sections.upload.delete.confirm.title' | translate"
|
|
||||||
title="{{ 'submission.sections.upload.delete.confirm.title' | translate }}"
|
|
||||||
[disabled]="(processingDelete$ | async)"
|
|
||||||
(click)="$event.preventDefault();confirmDelete(content);">
|
|
||||||
<i *ngIf="(processingDelete$ | async)" class="fas fa-circle-notch fa-spin fa-2x text-danger"></i>
|
|
||||||
<i *ngIf="!(processingDelete$ | async)" class="fa fa-trash fa-2x text-danger"></i>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
<ds-submission-section-upload-file-view *ngIf="readMode"
|
<ds-submission-section-upload-file-view [fileData]="fileData"></ds-submission-section-upload-file-view>
|
||||||
[fileData]="fileData"></ds-submission-section-upload-file-view>
|
|
||||||
<ds-submission-section-upload-file-edit *ngIf="!readMode"
|
|
||||||
[availableAccessConditionOptions]="availableAccessConditionOptions"
|
|
||||||
[collectionId]="collectionId"
|
|
||||||
[collectionPolicyType]="collectionPolicyType"
|
|
||||||
[configMetadataForm]="configMetadataForm"
|
|
||||||
[fileData]="fileData"
|
|
||||||
[fileId]="fileId"
|
|
||||||
[fileIndex]="fileIndex"
|
|
||||||
[formId]="formId"
|
|
||||||
[sectionId]="sectionId"></ds-submission-section-upload-file-edit>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
.sticky-buttons {
|
|
||||||
position: sticky;
|
|
||||||
top: calc(var(--bs-dropdown-item-padding-x) * 3);
|
|
||||||
z-index: var(--ds-submission-footer-z-index);
|
|
||||||
background-color: rgba(255, 255, 255, .97);
|
|
||||||
}
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { BrowserModule, By } from '@angular/platform-browser';
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of, of as observableOf } from 'rxjs';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { FormService } from '../../../../shared/form/form.service';
|
import { FormService } from '../../../../shared/form/form.service';
|
||||||
@@ -17,10 +17,8 @@ import { SubmissionJsonPatchOperationsService } from '../../../../core/submissio
|
|||||||
import { SubmissionSectionUploadFileComponent } from './section-upload-file.component';
|
import { SubmissionSectionUploadFileComponent } from './section-upload-file.component';
|
||||||
import { SubmissionServiceStub } from '../../../../shared/testing/submission-service.stub';
|
import { SubmissionServiceStub } from '../../../../shared/testing/submission-service.stub';
|
||||||
import {
|
import {
|
||||||
mockFileFormData,
|
|
||||||
mockSubmissionCollectionId,
|
mockSubmissionCollectionId,
|
||||||
mockSubmissionId,
|
mockSubmissionId,
|
||||||
mockSubmissionObject,
|
|
||||||
mockUploadConfigResponse,
|
mockUploadConfigResponse,
|
||||||
mockUploadFiles
|
mockUploadFiles
|
||||||
} from '../../../../shared/mocks/submission.mock';
|
} from '../../../../shared/mocks/submission.mock';
|
||||||
@@ -32,10 +30,19 @@ import { FileSizePipe } from '../../../../shared/utils/file-size-pipe';
|
|||||||
import { POLICY_DEFAULT_WITH_LIST } from '../section-upload.component';
|
import { POLICY_DEFAULT_WITH_LIST } from '../section-upload.component';
|
||||||
import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
import { getMockSectionUploadService } from '../../../../shared/mocks/section-upload.service.mock';
|
import { getMockSectionUploadService } from '../../../../shared/mocks/section-upload.service.mock';
|
||||||
import { FormFieldMetadataValueObject } from '../../../../shared/form/builder/models/form-field-metadata-value.model';
|
|
||||||
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
|
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
|
||||||
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
import { dateToISOFormat } from '../../../../shared/date.util';
|
|
||||||
|
const configMetadataFormMock = {
|
||||||
|
rows: [{
|
||||||
|
fields: [{
|
||||||
|
selectableMetadata: [
|
||||||
|
{metadata: 'dc.title', label: null, closed: false},
|
||||||
|
{metadata: 'dc.description', label: null, closed: false}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
describe('SubmissionSectionUploadFileComponent test suite', () => {
|
describe('SubmissionSectionUploadFileComponent test suite', () => {
|
||||||
|
|
||||||
@@ -117,6 +124,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
|
|||||||
|
|
||||||
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
testComp = testFixture.componentInstance;
|
testComp = testFixture.componentInstance;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -124,9 +132,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create SubmissionSectionUploadFileComponent', inject([SubmissionSectionUploadFileComponent], (app: SubmissionSectionUploadFileComponent) => {
|
it('should create SubmissionSectionUploadFileComponent', inject([SubmissionSectionUploadFileComponent], (app: SubmissionSectionUploadFileComponent) => {
|
||||||
|
|
||||||
expect(app).toBeDefined();
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -135,6 +141,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
|
|||||||
fixture = TestBed.createComponent(SubmissionSectionUploadFileComponent);
|
fixture = TestBed.createComponent(SubmissionSectionUploadFileComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
compAsAny = comp;
|
compAsAny = comp;
|
||||||
|
compAsAny.configMetadataForm = configMetadataFormMock;
|
||||||
submissionServiceStub = TestBed.inject(SubmissionService as any);
|
submissionServiceStub = TestBed.inject(SubmissionService as any);
|
||||||
uploadService = TestBed.inject(SectionUploadService);
|
uploadService = TestBed.inject(SectionUploadService);
|
||||||
formService = TestBed.inject(FormService);
|
formService = TestBed.inject(FormService);
|
||||||
@@ -210,96 +217,20 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
|
|||||||
pathCombiner.subRootElement);
|
pathCombiner.subRootElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save Bitstream File data properly when form is valid', fakeAsync(() => {
|
it('should open edit modal when edit button is clicked', () => {
|
||||||
compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent);
|
spyOn(compAsAny, 'editBitstreamData').and.callThrough();
|
||||||
compAsAny.fileEditComp.formRef = {formGroup: null};
|
comp.fileData = fileData;
|
||||||
compAsAny.pathCombiner = pathCombiner;
|
|
||||||
const event = new Event('click', null);
|
|
||||||
spyOn(comp, 'switchMode');
|
|
||||||
formService.validateAllFormFields.and.callFake(() => null);
|
|
||||||
formService.isValid.and.returnValue(observableOf(true));
|
|
||||||
formService.getFormData.and.returnValue(observableOf(mockFileFormData));
|
|
||||||
|
|
||||||
const response = [
|
fixture.detectChanges();
|
||||||
Object.assign(mockSubmissionObject, {
|
|
||||||
sections: {
|
|
||||||
upload: {
|
|
||||||
files: mockUploadFiles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
];
|
|
||||||
operationsService.jsonPatchByResourceID.and.returnValue(observableOf(response));
|
|
||||||
|
|
||||||
const accessConditionsToSave = [
|
const modalBtn = fixture.debugElement.query(By.css('.fa-edit '));
|
||||||
{ name: 'openaccess' },
|
|
||||||
{ name: 'lease', endDate: dateToISOFormat('2019-01-16T00:00:00Z') },
|
|
||||||
{ name: 'embargo', startDate: dateToISOFormat('2019-01-16T00:00:00Z') },
|
|
||||||
];
|
|
||||||
comp.saveBitstreamData(event);
|
|
||||||
tick();
|
|
||||||
|
|
||||||
let path = 'metadata/dc.title';
|
modalBtn.nativeElement.click();
|
||||||
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
fixture.detectChanges();
|
||||||
pathCombiner.getPath(path),
|
|
||||||
mockFileFormData.metadata['dc.title'],
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
path = 'metadata/dc.description';
|
expect(compAsAny.editBitstreamData).toHaveBeenCalled();
|
||||||
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
|
||||||
pathCombiner.getPath(path),
|
|
||||||
mockFileFormData.metadata['dc.description'],
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
path = 'accessConditions';
|
|
||||||
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
|
||||||
pathCombiner.getPath(path),
|
|
||||||
accessConditionsToSave,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(comp.switchMode).toHaveBeenCalled();
|
|
||||||
expect(uploadService.updateFileData).toHaveBeenCalledWith(submissionId, sectionId, mockUploadFiles[0].uuid, mockUploadFiles[0]);
|
|
||||||
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => {
|
|
||||||
compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent);
|
|
||||||
compAsAny.fileEditComp.formRef = {formGroup: null};
|
|
||||||
compAsAny.pathCombiner = pathCombiner;
|
|
||||||
const event = new Event('click', null);
|
|
||||||
spyOn(comp, 'switchMode');
|
|
||||||
formService.validateAllFormFields.and.callFake(() => null);
|
|
||||||
formService.isValid.and.returnValue(observableOf(false));
|
|
||||||
|
|
||||||
expect(comp.switchMode).not.toHaveBeenCalled();
|
|
||||||
expect(uploadService.updateFileData).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should retrieve Value From Field properly', () => {
|
|
||||||
let field;
|
|
||||||
expect(compAsAny.retrieveValueFromField(field)).toBeUndefined();
|
|
||||||
|
|
||||||
field = new FormFieldMetadataValueObject('test');
|
|
||||||
expect(compAsAny.retrieveValueFromField(field)).toBe('test');
|
|
||||||
|
|
||||||
field = [new FormFieldMetadataValueObject('test')];
|
|
||||||
expect(compAsAny.retrieveValueFromField(field)).toBe('test');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should switch read mode', () => {
|
|
||||||
comp.readMode = false;
|
|
||||||
|
|
||||||
comp.switchMode();
|
|
||||||
expect(comp.readMode).toBeTruthy();
|
|
||||||
|
|
||||||
comp.switchMode();
|
|
||||||
|
|
||||||
expect(comp.readMode).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -314,7 +245,7 @@ class TestComponent {
|
|||||||
availableAccessConditionOptions;
|
availableAccessConditionOptions;
|
||||||
collectionId = mockSubmissionCollectionId;
|
collectionId = mockSubmissionCollectionId;
|
||||||
collectionPolicyType;
|
collectionPolicyType;
|
||||||
configMetadataForm$;
|
configMetadataForm$ = of(configMetadataFormMock);
|
||||||
fileIndexes = [];
|
fileIndexes = [];
|
||||||
fileList = [];
|
fileList = [];
|
||||||
fileNames = [];
|
fileNames = [];
|
||||||
|
@@ -1,25 +1,23 @@
|
|||||||
import { ChangeDetectorRef, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core';
|
import { ChangeDetectorRef, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core';
|
||||||
|
|
||||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||||
import { filter, mergeMap, take } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
import { DynamicFormControlModel, } from '@ng-dynamic-forms/core';
|
import { DynamicFormControlModel, } from '@ng-dynamic-forms/core';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
import { SectionUploadService } from '../section-upload.service';
|
import { SectionUploadService } from '../section-upload.service';
|
||||||
import { isNotEmpty, isNotNull, isNotUndefined } from '../../../../shared/empty.util';
|
import { hasValue, isNotUndefined } from '../../../../shared/empty.util';
|
||||||
import { FormService } from '../../../../shared/form/form.service';
|
import { FormService } from '../../../../shared/form/form.service';
|
||||||
import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder';
|
import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder';
|
||||||
import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
import { WorkspaceitemSectionUploadFileObject } from '../../../../core/submission/models/workspaceitem-section-upload-file.model';
|
import { WorkspaceitemSectionUploadFileObject } from '../../../../core/submission/models/workspaceitem-section-upload-file.model';
|
||||||
import { SubmissionFormsModel } from '../../../../core/config/models/config-submission-forms.model';
|
import { SubmissionFormsModel } from '../../../../core/config/models/config-submission-forms.model';
|
||||||
import { dateToISOFormat } from '../../../../shared/date.util';
|
|
||||||
import { SubmissionService } from '../../../submission.service';
|
import { SubmissionService } from '../../../submission.service';
|
||||||
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
|
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
|
||||||
import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service';
|
import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service';
|
||||||
import { SubmissionObject } from '../../../../core/submission/models/submission-object.model';
|
|
||||||
import { WorkspaceitemSectionUploadObject } from '../../../../core/submission/models/workspaceitem-section-upload.model';
|
|
||||||
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
|
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
|
||||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||||
|
import { NgbModalOptions } from '@ng-bootstrap/ng-bootstrap/modal/modal-config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component represents a single bitstream contained in the submission
|
* This component represents a single bitstream contained in the submission
|
||||||
@@ -87,6 +85,13 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() submissionId: string;
|
@Input() submissionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [[SubmissionSectionUploadFileEditComponent]] reference
|
||||||
|
* @type {SubmissionSectionUploadFileEditComponent}
|
||||||
|
*/
|
||||||
|
@ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The bitstream's metadata data
|
* The bitstream's metadata data
|
||||||
* @type {WorkspaceitemSectionUploadFileObject}
|
* @type {WorkspaceitemSectionUploadFileObject}
|
||||||
@@ -130,10 +135,10 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit {
|
|||||||
protected subscriptions: Subscription[] = [];
|
protected subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [[SubmissionSectionUploadFileEditComponent]] reference
|
* Array containing all the form metadata defined in configMetadataForm
|
||||||
* @type {SubmissionSectionUploadFileEditComponent}
|
* @type {Array}
|
||||||
*/
|
*/
|
||||||
@ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent;
|
protected formMetadata: string[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize instance variables
|
* Initialize instance variables
|
||||||
@@ -147,14 +152,16 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit {
|
|||||||
* @param {SubmissionService} submissionService
|
* @param {SubmissionService} submissionService
|
||||||
* @param {SectionUploadService} uploadService
|
* @param {SectionUploadService} uploadService
|
||||||
*/
|
*/
|
||||||
constructor(private cdr: ChangeDetectorRef,
|
constructor(
|
||||||
private formService: FormService,
|
private cdr: ChangeDetectorRef,
|
||||||
private halService: HALEndpointService,
|
private formService: FormService,
|
||||||
private modalService: NgbModal,
|
private halService: HALEndpointService,
|
||||||
private operationsBuilder: JsonPatchOperationsBuilder,
|
private modalService: NgbModal,
|
||||||
private operationsService: SubmissionJsonPatchOperationsService,
|
private operationsBuilder: JsonPatchOperationsBuilder,
|
||||||
private submissionService: SubmissionService,
|
private operationsService: SubmissionJsonPatchOperationsService,
|
||||||
private uploadService: SectionUploadService) {
|
private submissionService: SubmissionService,
|
||||||
|
private uploadService: SectionUploadService,
|
||||||
|
) {
|
||||||
this.readMode = true;
|
this.readMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,22 +189,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.formId = this.formService.getUniqueId(this.fileId);
|
this.formId = this.formService.getUniqueId(this.fileId);
|
||||||
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex);
|
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex);
|
||||||
}
|
this.loadFormMetadata();
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete bitstream from submission
|
|
||||||
*/
|
|
||||||
protected deleteFile() {
|
|
||||||
this.operationsBuilder.remove(this.pathCombiner.getPath());
|
|
||||||
this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
|
|
||||||
this.submissionService.getSubmissionObjectLinkName(),
|
|
||||||
this.submissionId,
|
|
||||||
this.pathCombiner.rootElement,
|
|
||||||
this.pathCombiner.subRootElement)
|
|
||||||
.subscribe(() => {
|
|
||||||
this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId);
|
|
||||||
this.processingDelete$.next(false);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,98 +217,63 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
editBitstreamData() {
|
||||||
* Save bitstream metadata
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* the click event emitted
|
|
||||||
*/
|
|
||||||
public saveBitstreamData(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// validate form
|
const options: NgbModalOptions = {
|
||||||
this.formService.validateAllFormFields(this.fileEditComp.formRef.formGroup);
|
size: 'xl',
|
||||||
this.subscriptions.push(this.formService.isValid(this.formId).pipe(
|
backdrop: 'static',
|
||||||
take(1),
|
};
|
||||||
filter((isValid) => isValid),
|
|
||||||
mergeMap(() => this.formService.getFormData(this.formId)),
|
const activeModal = this.modalService.open(SubmissionSectionUploadFileEditComponent, options);
|
||||||
take(1),
|
|
||||||
mergeMap((formData: any) => {
|
activeModal.componentInstance.availableAccessConditionOptions = this.availableAccessConditionOptions;
|
||||||
// collect bitstream metadata
|
activeModal.componentInstance.collectionId = this.collectionId;
|
||||||
Object.keys((formData.metadata))
|
activeModal.componentInstance.collectionPolicyType = this.collectionPolicyType;
|
||||||
.filter((key) => isNotEmpty(formData.metadata[key]))
|
activeModal.componentInstance.configMetadataForm = this.configMetadataForm;
|
||||||
.forEach((key) => {
|
activeModal.componentInstance.fileData = this.fileData;
|
||||||
const metadataKey = key.replace(/_/g, '.');
|
activeModal.componentInstance.fileId = this.fileId;
|
||||||
const path = `metadata/${metadataKey}`;
|
activeModal.componentInstance.fileIndex = this.fileIndex;
|
||||||
this.operationsBuilder.add(this.pathCombiner.getPath(path), formData.metadata[key], true);
|
activeModal.componentInstance.formId = this.formId;
|
||||||
|
activeModal.componentInstance.sectionId = this.sectionId;
|
||||||
|
activeModal.componentInstance.formMetadata = this.formMetadata;
|
||||||
|
activeModal.componentInstance.pathCombiner = this.pathCombiner;
|
||||||
|
activeModal.componentInstance.submissionId = this.submissionId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.unsubscribeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribeAll() {
|
||||||
|
this.subscriptions.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadFormMetadata() {
|
||||||
|
this.configMetadataForm.rows.forEach((row) => {
|
||||||
|
row.fields.forEach((field) => {
|
||||||
|
field.selectableMetadata.forEach((metadatum) => {
|
||||||
|
this.formMetadata.push(metadatum.metadata);
|
||||||
});
|
});
|
||||||
const accessConditionsToSave = [];
|
});
|
||||||
formData.accessConditions
|
|
||||||
.map((accessConditions) => accessConditions.accessConditionGroup)
|
|
||||||
.filter((accessCondition) => isNotEmpty(accessCondition))
|
|
||||||
.forEach((accessCondition) => {
|
|
||||||
let accessConditionOpt;
|
|
||||||
|
|
||||||
this.availableAccessConditionOptions
|
|
||||||
.filter((element) => isNotNull(accessCondition.name) && element.name === accessCondition.name[0].value)
|
|
||||||
.forEach((element) => accessConditionOpt = element);
|
|
||||||
|
|
||||||
if (accessConditionOpt) {
|
|
||||||
accessConditionOpt = Object.assign({}, accessCondition);
|
|
||||||
accessConditionOpt.name = this.retrieveValueFromField(accessCondition.name);
|
|
||||||
if (accessCondition.startDate) {
|
|
||||||
const startDate = this.retrieveValueFromField(accessCondition.startDate);
|
|
||||||
accessConditionOpt.startDate = dateToISOFormat(startDate);
|
|
||||||
}
|
|
||||||
if (accessCondition.endDate) {
|
|
||||||
const endDate = this.retrieveValueFromField(accessCondition.endDate);
|
|
||||||
accessConditionOpt.endDate = dateToISOFormat(endDate);
|
|
||||||
}
|
|
||||||
accessConditionsToSave.push(accessConditionOpt);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isNotEmpty(accessConditionsToSave)) {
|
|
||||||
this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// dispatch a PATCH request to save metadata
|
|
||||||
return this.operationsService.jsonPatchByResourceID(
|
|
||||||
this.submissionService.getSubmissionObjectLinkName(),
|
|
||||||
this.submissionId,
|
|
||||||
this.pathCombiner.rootElement,
|
|
||||||
this.pathCombiner.subRootElement);
|
|
||||||
})
|
|
||||||
).subscribe((result: SubmissionObject[]) => {
|
|
||||||
if (result[0].sections[this.sectionId]) {
|
|
||||||
const uploadSection = (result[0].sections[this.sectionId] as WorkspaceitemSectionUploadObject);
|
|
||||||
Object.keys(uploadSection.files)
|
|
||||||
.filter((key) => uploadSection.files[key].uuid === this.fileId)
|
|
||||||
.forEach((key) => this.uploadService.updateFileData(
|
|
||||||
this.submissionId, this.sectionId, this.fileId, uploadSection.files[key])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this.switchMode();
|
);
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve field value
|
* Delete bitstream from submission
|
||||||
*
|
|
||||||
* @param field
|
|
||||||
* the specified field object
|
|
||||||
*/
|
*/
|
||||||
private retrieveValueFromField(field: any) {
|
protected deleteFile() {
|
||||||
const temp = Array.isArray(field) ? field[0] : field;
|
this.operationsBuilder.remove(this.pathCombiner.getPath());
|
||||||
return (temp) ? temp.value : undefined;
|
this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
|
||||||
}
|
this.submissionService.getSubmissionObjectLinkName(),
|
||||||
|
this.submissionId,
|
||||||
/**
|
this.pathCombiner.rootElement,
|
||||||
* Switch from edit form to metadata view
|
this.pathCombiner.subRootElement)
|
||||||
*/
|
.subscribe(() => {
|
||||||
public switchMode() {
|
this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId);
|
||||||
this.readMode = !this.readMode;
|
this.processingDelete$.next(false);
|
||||||
this.cdr.detectChanges();
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
|
import { makeStateKey } from '@angular/platform-browser';
|
||||||
import { Config } from './config.interface';
|
import { Config } from './config.interface';
|
||||||
import { ServerConfig } from './server-config.interface';
|
import { ServerConfig } from './server-config.interface';
|
||||||
import { CacheConfig } from './cache-config.interface';
|
import { CacheConfig } from './cache-config.interface';
|
||||||
@@ -7,14 +8,13 @@ import { INotificationBoardOptions } from './notifications-config.interfaces';
|
|||||||
import { SubmissionConfig } from './submission-config.interface';
|
import { SubmissionConfig } from './submission-config.interface';
|
||||||
import { FormConfig } from './form-config.interfaces';
|
import { FormConfig } from './form-config.interfaces';
|
||||||
import { LangConfig } from './lang-config.interface';
|
import { LangConfig } from './lang-config.interface';
|
||||||
import { BrowseByConfig } from './browse-by-config.interface';
|
|
||||||
import { ItemPageConfig } from './item-page-config.interface';
|
import { ItemPageConfig } from './item-page-config.interface';
|
||||||
import { CollectionPageConfig } from './collection-page-config.interface';
|
import { CollectionPageConfig } from './collection-page-config.interface';
|
||||||
import { ThemeConfig } from './theme.model';
|
import { ThemeConfig } from './theme.model';
|
||||||
import { AuthConfig } from './auth-config.interfaces';
|
import { AuthConfig } from './auth-config.interfaces';
|
||||||
import { UIServerConfig } from './ui-server-config.interface';
|
import { UIServerConfig } from './ui-server-config.interface';
|
||||||
import { MediaViewerConfig } from './media-viewer-config.interface';
|
import { MediaViewerConfig } from './media-viewer-config.interface';
|
||||||
import { makeStateKey } from '@angular/platform-browser';
|
import { BrowseByConfig } from './browse-by-config.interface';
|
||||||
|
|
||||||
interface AppConfig extends Config {
|
interface AppConfig extends Config {
|
||||||
ui: UIServerConfig;
|
ui: UIServerConfig;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { Config } from './config.interface';
|
import { Config } from './config.interface';
|
||||||
import { BrowseByTypeConfig } from './browse-by-type-config.interface';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config that determines how the dropdown list of years are created for browse-by-date components
|
* Config that determines how the dropdown list of years are created for browse-by-date components
|
||||||
@@ -19,9 +18,4 @@ export interface BrowseByConfig extends Config {
|
|||||||
* The absolute lowest year to display in the dropdown when no lowest date can be found for all items
|
* The absolute lowest year to display in the dropdown when no lowest date can be found for all items
|
||||||
*/
|
*/
|
||||||
defaultLowerLimit: number;
|
defaultLowerLimit: number;
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all the active Browse-By pages
|
|
||||||
*/
|
|
||||||
types: BrowseByTypeConfig[];
|
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +0,0 @@
|
|||||||
import { Config } from './config.interface';
|
|
||||||
import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Config used for rendering Browse-By pages and links
|
|
||||||
*/
|
|
||||||
export interface BrowseByTypeConfig extends Config {
|
|
||||||
/**
|
|
||||||
* The browse id used for fetching browse data from the rest api
|
|
||||||
* e.g. author
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of Browse-By page to render
|
|
||||||
*/
|
|
||||||
type: BrowseByType | string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The metadata field to use for rendering starts-with options (only necessary when type is set to BrowseByType.Date)
|
|
||||||
*/
|
|
||||||
metadataField?: string;
|
|
||||||
}
|
|
@@ -1,4 +1,3 @@
|
|||||||
import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator';
|
|
||||||
import { RestRequestMethod } from '../app/core/data/rest-request-method';
|
import { RestRequestMethod } from '../app/core/data/rest-request-method';
|
||||||
import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type';
|
import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type';
|
||||||
import { AppConfig } from './app-config.interface';
|
import { AppConfig } from './app-config.interface';
|
||||||
@@ -200,33 +199,7 @@ export class DefaultAppConfig implements AppConfig {
|
|||||||
// Limit for years to display using jumps of five years (current year - fiveYearLimit)
|
// Limit for years to display using jumps of five years (current year - fiveYearLimit)
|
||||||
fiveYearLimit: 30,
|
fiveYearLimit: 30,
|
||||||
// The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
|
// The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
|
||||||
defaultLowerLimit: 1900,
|
defaultLowerLimit: 1900
|
||||||
// List of all the active Browse-By types
|
|
||||||
// Adding a type will activate their Browse-By page and add them to the global navigation menu,
|
|
||||||
// as well as community and collection pages
|
|
||||||
// Allowed fields and their purpose:
|
|
||||||
// id: The browse id to use for fetching info from the rest api
|
|
||||||
// type: The type of Browse-By page to display
|
|
||||||
// metadataField: The metadata-field used to create starts-with options (only necessary when the type is set to 'date')
|
|
||||||
types: [
|
|
||||||
{
|
|
||||||
id: 'title',
|
|
||||||
type: BrowseByType.Title,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'dateissued',
|
|
||||||
type: BrowseByType.Date,
|
|
||||||
metadataField: 'dc.date.issued'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'author',
|
|
||||||
type: BrowseByType.Metadata
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'subject',
|
|
||||||
type: BrowseByType.Metadata
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Item Page Config
|
// Item Page Config
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
// This configuration is only used for unit tests, end-to-end tests use environment.production.ts
|
// This configuration is only used for unit tests, end-to-end tests use environment.production.ts
|
||||||
import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator';
|
|
||||||
import { RestRequestMethod } from '../app/core/data/rest-request-method';
|
import { RestRequestMethod } from '../app/core/data/rest-request-method';
|
||||||
import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type';
|
import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type';
|
||||||
import { AppConfig } from '../config/app-config.interface';
|
import { AppConfig } from '../config/app-config.interface';
|
||||||
@@ -189,32 +188,6 @@ export const environment: AppConfig = {
|
|||||||
fiveYearLimit: 30,
|
fiveYearLimit: 30,
|
||||||
// The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
|
// The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
|
||||||
defaultLowerLimit: 1900,
|
defaultLowerLimit: 1900,
|
||||||
// List of all the active Browse-By types
|
|
||||||
// Adding a type will activate their Browse-By page and add them to the global navigation menu,
|
|
||||||
// as well as community and collection pages
|
|
||||||
// Allowed fields and their purpose:
|
|
||||||
// id: The browse id to use for fetching info from the rest api
|
|
||||||
// type: The type of Browse-By page to display
|
|
||||||
// metadataField: The metadata-field used to create starts-with options (only necessary when the type is set to 'date')
|
|
||||||
types: [
|
|
||||||
{
|
|
||||||
id: 'title',
|
|
||||||
type: BrowseByType.Title,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'dateissued',
|
|
||||||
type: BrowseByType.Date,
|
|
||||||
metadataField: 'dc.date.issued'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'author',
|
|
||||||
type: BrowseByType.Metadata
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'subject',
|
|
||||||
type: BrowseByType.Metadata
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
edit: {
|
edit: {
|
||||||
|
Reference in New Issue
Block a user