mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Merge remote-tracking branch 'dspace/main' into process-admin-ui-redesign-8.0.0-next
This commit is contained in:
@@ -285,8 +285,17 @@ item:
|
|||||||
# settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.
|
# settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
|
|
||||||
|
# Community Page Config
|
||||||
|
community:
|
||||||
|
# Search tab config
|
||||||
|
searchSection:
|
||||||
|
showSidebar: true
|
||||||
|
|
||||||
# Collection Page Config
|
# Collection Page Config
|
||||||
collection:
|
collection:
|
||||||
|
# Search tab config
|
||||||
|
searchSection:
|
||||||
|
showSidebar: true
|
||||||
edit:
|
edit:
|
||||||
undoTimeout: 10000 # 10 seconds
|
undoTimeout: 10000 # 10 seconds
|
||||||
|
|
||||||
@@ -391,4 +400,3 @@ comcolSelectionSort:
|
|||||||
# suggestion:
|
# suggestion:
|
||||||
# - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af
|
# - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af
|
||||||
# source: "openaire"
|
# source: "openaire"
|
||||||
|
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
:host {
|
|
||||||
::ng-deep {
|
|
||||||
.switch {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(var(--bs-spacer) * 2.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:host ::ng-deep ds-dynamic-form-control-container > div > label {
|
|
||||||
margin-top: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@@ -2,14 +2,35 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnIni
|
|||||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
import {
|
||||||
import { DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicInputModel, DynamicSelectModel } from '@ng-dynamic-forms/core';
|
combineLatest,
|
||||||
|
combineLatest as observableCombineLatest,
|
||||||
|
Observable,
|
||||||
|
of as observableOf,
|
||||||
|
Subscription
|
||||||
|
} from 'rxjs';
|
||||||
|
import {
|
||||||
|
DynamicFormControlModel,
|
||||||
|
DynamicFormGroupModel,
|
||||||
|
DynamicFormLayout,
|
||||||
|
DynamicFormService,
|
||||||
|
DynamicInputModel,
|
||||||
|
DynamicSelectModel
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
import { UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormGroup } from '@angular/forms';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
import {
|
||||||
|
DynamicCustomSwitchModel
|
||||||
|
} from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
import { getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload, getRemoteDataPayload } from '../../core/shared/operators';
|
import {
|
||||||
|
getAllSucceededRemoteDataPayload,
|
||||||
|
getFirstCompletedRemoteData,
|
||||||
|
getFirstSucceededRemoteData,
|
||||||
|
getFirstSucceededRemoteDataPayload,
|
||||||
|
getRemoteDataPayload
|
||||||
|
} from '../../core/shared/operators';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
|
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
|
||||||
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
|
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
|
||||||
@@ -245,7 +266,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* All input models in a simple array for easier iterations
|
* All input models in a simple array for easier iterations
|
||||||
*/
|
*/
|
||||||
inputModels = [this.fileNameModel, this.primaryBitstreamModel, this.descriptionModel, this.selectedFormatModel,
|
inputModels = [this.primaryBitstreamModel, this.fileNameModel, this.descriptionModel, this.selectedFormatModel,
|
||||||
this.newFormatModel];
|
this.newFormatModel];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,8 +277,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
new DynamicFormGroupModel({
|
new DynamicFormGroupModel({
|
||||||
id: 'fileNamePrimaryContainer',
|
id: 'fileNamePrimaryContainer',
|
||||||
group: [
|
group: [
|
||||||
this.fileNameModel,
|
this.primaryBitstreamModel,
|
||||||
this.primaryBitstreamModel
|
this.fileNameModel
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
grid: {
|
grid: {
|
||||||
@@ -295,7 +316,10 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
primaryBitstream: {
|
primaryBitstream: {
|
||||||
grid: {
|
grid: {
|
||||||
host: 'col col-sm-4 d-inline-block switch border-0'
|
container: 'col-12'
|
||||||
|
},
|
||||||
|
element: {
|
||||||
|
container: 'text-right'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
|
@@ -25,7 +25,7 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||||
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
||||||
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||||
import { CollectionRecentlyAddedComponent } from './sections/recently-added/collection-recently-added.component';
|
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -73,7 +73,7 @@ import { CollectionRecentlyAddedComponent } from './sections/recently-added/coll
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
component: CollectionRecentlyAddedComponent,
|
component: ComcolSearchSectionComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'browse/:id',
|
path: 'browse/:id',
|
||||||
|
@@ -19,7 +19,6 @@ import { ComcolModule } from '../shared/comcol/comcol.module';
|
|||||||
import { DsoSharedModule } from '../dso-shared/dso-shared.module';
|
import { DsoSharedModule } from '../dso-shared/dso-shared.module';
|
||||||
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
||||||
import { BrowseByPageModule } from '../browse-by/browse-by-page.module';
|
import { BrowseByPageModule } from '../browse-by/browse-by-page.module';
|
||||||
import { CollectionRecentlyAddedComponent } from './sections/recently-added/collection-recently-added.component';
|
|
||||||
|
|
||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
CollectionPageComponent,
|
CollectionPageComponent,
|
||||||
@@ -29,7 +28,6 @@ const DECLARATIONS = [
|
|||||||
EditItemTemplatePageComponent,
|
EditItemTemplatePageComponent,
|
||||||
ThemedEditItemTemplatePageComponent,
|
ThemedEditItemTemplatePageComponent,
|
||||||
CollectionItemMapperComponent,
|
CollectionItemMapperComponent,
|
||||||
CollectionRecentlyAddedComponent,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
<ng-container *ngVar="(itemRD$ | async) as itemRD">
|
|
||||||
<div class="mt-4" *ngIf="itemRD?.hasSucceeded" @fadeIn>
|
|
||||||
<h3 class="sr-only">{{'collection.page.browse.recent.head' | translate}}</h3>
|
|
||||||
<ds-viewable-collection
|
|
||||||
[config]="paginationConfig"
|
|
||||||
[sortConfig]="sortConfig"
|
|
||||||
[objects]="itemRD"
|
|
||||||
[hideGear]="true">
|
|
||||||
</ds-viewable-collection>
|
|
||||||
</div>
|
|
||||||
<ds-error *ngIf="itemRD?.hasFailed"
|
|
||||||
message="{{'error.recent-submissions' | translate}}"></ds-error>
|
|
||||||
<ds-themed-loading *ngIf="!itemRD || itemRD.isLoading"
|
|
||||||
message="{{'loading.recent-submissions' | translate}}"></ds-themed-loading>
|
|
||||||
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
|
|
||||||
{{'collection.page.browse.recent.empty' | translate}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
@@ -1,53 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { CollectionRecentlyAddedComponent } from './collection-recently-added.component';
|
|
||||||
import { APP_CONFIG } from '../../../../config/app-config.interface';
|
|
||||||
import { environment } from '../../../../environments/environment.test';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
|
||||||
import { SearchServiceStub } from '../../../shared/testing/search-service.stub';
|
|
||||||
import { SearchService } from '../../../core/shared/search/search.service';
|
|
||||||
import { VarDirective } from '../../../shared/utils/var.directive';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
||||||
|
|
||||||
describe('CollectionRecentlyAddedComponent', () => {
|
|
||||||
let component: CollectionRecentlyAddedComponent;
|
|
||||||
let fixture: ComponentFixture<CollectionRecentlyAddedComponent>;
|
|
||||||
|
|
||||||
let activatedRoute: ActivatedRouteStub;
|
|
||||||
let paginationService: PaginationServiceStub;
|
|
||||||
let searchService: SearchServiceStub;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
activatedRoute = new ActivatedRouteStub();
|
|
||||||
paginationService = new PaginationServiceStub();
|
|
||||||
searchService = new SearchServiceStub();
|
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [
|
|
||||||
CollectionRecentlyAddedComponent,
|
|
||||||
VarDirective,
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
TranslateModule.forRoot(),
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: ActivatedRoute, useValue: activatedRoute },
|
|
||||||
{ provide: APP_CONFIG, useValue: environment },
|
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
|
||||||
{ provide: SearchService, useValue: SearchServiceStub },
|
|
||||||
],
|
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(CollectionRecentlyAddedComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,82 +0,0 @@
|
|||||||
import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
|
|
||||||
import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
|
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
|
||||||
import { Item } from '../../../core/shared/item.model';
|
|
||||||
import { switchMap, map, startWith, take } from 'rxjs/operators';
|
|
||||||
import { getFirstSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators';
|
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
|
||||||
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
|
||||||
import { BROWSE_LINKS_TO_FOLLOW } from '../../../core/browse/browse.service';
|
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { SortOptions, SortDirection } from '../../../core/cache/models/sort-options.model';
|
|
||||||
import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
|
|
||||||
import { SearchService } from '../../../core/shared/search/search.service';
|
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
|
||||||
import { ActivatedRoute, Data } from '@angular/router';
|
|
||||||
import { fadeIn } from '../../../shared/animations/fade';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-collection-recently-added',
|
|
||||||
templateUrl: './collection-recently-added.component.html',
|
|
||||||
styleUrls: ['./collection-recently-added.component.scss'],
|
|
||||||
animations: [fadeIn],
|
|
||||||
})
|
|
||||||
export class CollectionRecentlyAddedComponent implements OnInit, OnDestroy {
|
|
||||||
|
|
||||||
paginationConfig: PaginationComponentOptions;
|
|
||||||
|
|
||||||
sortConfig: SortOptions;
|
|
||||||
|
|
||||||
collectionRD$: Observable<RemoteData<Collection>>;
|
|
||||||
|
|
||||||
itemRD$: Observable<RemoteData<PaginatedList<Item>>>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
|
||||||
protected paginationService: PaginationService,
|
|
||||||
protected route: ActivatedRoute,
|
|
||||||
protected searchService: SearchService,
|
|
||||||
) {
|
|
||||||
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
|
||||||
id: 'cp',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: this.appConfig.browseBy.pageSize,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.collectionRD$ = this.route.data.pipe(
|
|
||||||
map((data: Data) => data.dso as RemoteData<Collection>),
|
|
||||||
take(1),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.itemRD$ = observableCombineLatest([
|
|
||||||
this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig),
|
|
||||||
this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig),
|
|
||||||
]).pipe(
|
|
||||||
switchMap(([currentPagination, currentSort]: [PaginationComponentOptions, SortOptions]) => this.collectionRD$.pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
map((rd: RemoteData<Collection>) => rd.payload.id),
|
|
||||||
switchMap((id: string) => this.searchService.search<Item>(
|
|
||||||
new PaginatedSearchOptions({
|
|
||||||
scope: id,
|
|
||||||
pagination: currentPagination,
|
|
||||||
sort: currentSort,
|
|
||||||
dsoTypes: [DSpaceObjectType.ITEM]
|
|
||||||
}), null, true, true, ...BROWSE_LINKS_TO_FOLLOW).pipe(
|
|
||||||
toDSpaceObjectListRD()
|
|
||||||
) as Observable<RemoteData<PaginatedList<Item>>>),
|
|
||||||
startWith(undefined), // Make sure switching pages shows loading component
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.paginationService.clearPagination(this.paginationConfig.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -19,6 +19,8 @@ import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-co
|
|||||||
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||||
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
||||||
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||||
|
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
|
||||||
|
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -56,7 +58,16 @@ import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
|
component: ComcolSearchSectionComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'subcoms-cols',
|
||||||
|
pathMatch: 'full',
|
||||||
component: SubComColSectionComponent,
|
component: SubComColSectionComponent,
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: I18nBreadcrumbResolver,
|
||||||
|
},
|
||||||
|
data: { breadcrumbKey: 'community.subcoms-cols' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'browse/:id',
|
path: 'browse/:id',
|
||||||
|
@@ -11,6 +11,7 @@ describe('SubComColSectionComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
activatedRoute = new ActivatedRouteStub();
|
activatedRoute = new ActivatedRouteStub();
|
||||||
|
activatedRoute.parent = new ActivatedRouteStub();
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -20,7 +20,7 @@ export class SubComColSectionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.community$ = this.route.data.pipe(
|
this.community$ = this.route.parent.data.pipe(
|
||||||
map((data: Data) => (data.dso as RemoteData<Community>).payload),
|
map((data: Data) => (data.dso as RemoteData<Community>).payload),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,11 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import objectContaining = jasmine.objectContaining;
|
import objectContaining = jasmine.objectContaining;
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
import { BundleDataService } from './bundle-data.service';
|
||||||
|
import { ItemMock } from 'src/app/shared/mocks/item.mock';
|
||||||
|
import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from 'src/app/shared/remote-data.utils';
|
||||||
|
import { Bundle } from '../shared/bundle.model';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
describe('BitstreamDataService', () => {
|
describe('BitstreamDataService', () => {
|
||||||
let service: BitstreamDataService;
|
let service: BitstreamDataService;
|
||||||
@@ -29,6 +34,7 @@ describe('BitstreamDataService', () => {
|
|||||||
let halService: HALEndpointService;
|
let halService: HALEndpointService;
|
||||||
let bitstreamFormatService: BitstreamFormatDataService;
|
let bitstreamFormatService: BitstreamFormatDataService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let bundleDataService: BundleDataService;
|
||||||
const bitstreamFormatHref = 'rest-api/bitstreamformats';
|
const bitstreamFormatHref = 'rest-api/bitstreamformats';
|
||||||
|
|
||||||
const bitstream1 = Object.assign(new Bitstream(), {
|
const bitstream1 = Object.assign(new Bitstream(), {
|
||||||
@@ -62,6 +68,7 @@ describe('BitstreamDataService', () => {
|
|||||||
bitstreamFormatService = jasmine.createSpyObj('bistreamFormatService', {
|
bitstreamFormatService = jasmine.createSpyObj('bistreamFormatService', {
|
||||||
getBrowseEndpoint: observableOf(bitstreamFormatHref)
|
getBrowseEndpoint: observableOf(bitstreamFormatHref)
|
||||||
});
|
});
|
||||||
|
|
||||||
rdbService = getMockRemoteDataBuildService();
|
rdbService = getMockRemoteDataBuildService();
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -76,6 +83,7 @@ describe('BitstreamDataService', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
service = TestBed.inject(BitstreamDataService);
|
service = TestBed.inject(BitstreamDataService);
|
||||||
|
bundleDataService = TestBed.inject(BundleDataService);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('composition', () => {
|
describe('composition', () => {
|
||||||
@@ -118,6 +126,32 @@ describe('BitstreamDataService', () => {
|
|||||||
expect(service.invalidateByHref).toHaveBeenCalledWith('fake-bitstream1-self');
|
expect(service.invalidateByHref).toHaveBeenCalledWith('fake-bitstream1-self');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('findPrimaryBitstreamByItemAndName', () => {
|
||||||
|
it('should return primary bitstream', () => {
|
||||||
|
const exprected$ = cold('(a|)', { a: bitstream1} );
|
||||||
|
const bundle = Object.assign(new Bundle(), {
|
||||||
|
primaryBitstream: observableOf(createSuccessfulRemoteDataObject(bitstream1)),
|
||||||
|
});
|
||||||
|
spyOn(bundleDataService, 'findByItemAndName').and.returnValue(observableOf(createSuccessfulRemoteDataObject(bundle)));
|
||||||
|
expect(service.findPrimaryBitstreamByItemAndName(ItemMock, 'ORIGINAL')).toBeObservable(exprected$);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null if primary bitstream has not be succeeded ', () => {
|
||||||
|
const exprected$ = cold('(a|)', { a: null} );
|
||||||
|
const bundle = Object.assign(new Bundle(), {
|
||||||
|
primaryBitstream: observableOf(createFailedRemoteDataObject()),
|
||||||
|
});
|
||||||
|
spyOn(bundleDataService, 'findByItemAndName').and.returnValue(observableOf(createSuccessfulRemoteDataObject(bundle)));
|
||||||
|
expect(service.findPrimaryBitstreamByItemAndName(ItemMock, 'ORIGINAL')).toBeObservable(exprected$);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return EMPTY if nothing where found', () => {
|
||||||
|
const exprected$ = cold('(|)', {} );
|
||||||
|
spyOn(bundleDataService, 'findByItemAndName').and.returnValue(observableOf(createFailedRemoteDataObject<Bundle>()));
|
||||||
|
expect(service.findPrimaryBitstreamByItemAndName(ItemMock, 'ORIGINAL')).toBeObservable(exprected$);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to delete multiple bitstreams', () => {
|
it('should be able to delete multiple bitstreams', () => {
|
||||||
service.removeMultiple([bitstream1, bitstream2]);
|
service.removeMultiple([bitstream1, bitstream2]);
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { HttpHeaders } from '@angular/common/http';
|
import { HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable, EMPTY } from 'rxjs';
|
||||||
import { find, map, switchMap, take } from 'rxjs/operators';
|
import { find, map, switchMap, take } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig, followLink } from '../../shared/utils/follow-link-config.model';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { Bitstream } from '../shared/bitstream.model';
|
import { Bitstream } from '../shared/bitstream.model';
|
||||||
@@ -34,6 +34,7 @@ import { NoContent } from '../shared/NoContent.model';
|
|||||||
import { IdentifiableDataService } from './base/identifiable-data.service';
|
import { IdentifiableDataService } from './base/identifiable-data.service';
|
||||||
import { dataService } from './base/data-service.decorator';
|
import { dataService } from './base/data-service.decorator';
|
||||||
import { Operation, RemoveOperation } from 'fast-json-patch';
|
import { Operation, RemoveOperation } from 'fast-json-patch';
|
||||||
|
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service to retrieve {@link Bitstream}s from the REST API
|
* A service to retrieve {@link Bitstream}s from the REST API
|
||||||
@@ -201,6 +202,37 @@ export class BitstreamDataService extends IdentifiableDataService<Bitstream> imp
|
|||||||
return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow);
|
return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Make a request to get primary bitstream
|
||||||
|
* in all current use cases, and having it simplifies this method
|
||||||
|
*
|
||||||
|
* @param item the {@link Item} the {@link Bundle} is a part of
|
||||||
|
* @param bundleName the name of the {@link Bundle} we want to find
|
||||||
|
* {@link Bitstream}s for
|
||||||
|
* @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
|
||||||
|
* @return {Observable<Bitstream | null>}
|
||||||
|
* Return an observable that constains primary bitstream information or null
|
||||||
|
*/
|
||||||
|
public findPrimaryBitstreamByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true): Observable<Bitstream | null> {
|
||||||
|
return this.bundleService.findByItemAndName(item, bundleName, useCachedVersionIfAvailable, reRequestOnStale, followLink('primaryBitstream')).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
switchMap((rd: RemoteData<Bundle>) => {
|
||||||
|
if (!rd.hasSucceeded) {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
return rd.payload.primaryBitstream.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
map((rdb: RemoteData<Bitstream>) => rdb.hasSucceeded ? rdb.payload : null)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a new FindListRequest with given search method
|
* Make a new FindListRequest with given search method
|
||||||
*
|
*
|
||||||
|
@@ -4,7 +4,10 @@ import { WorkspaceitemSectionUploadFileObject } from './workspaceitem-section-up
|
|||||||
* An interface to represent submission's upload section data.
|
* An interface to represent submission's upload section data.
|
||||||
*/
|
*/
|
||||||
export interface WorkspaceitemSectionUploadObject {
|
export interface WorkspaceitemSectionUploadObject {
|
||||||
|
/**
|
||||||
|
* Primary bitstream flag
|
||||||
|
*/
|
||||||
|
primary: string | null;
|
||||||
/**
|
/**
|
||||||
* A list of [[WorkspaceitemSectionUploadFileObject]]
|
* A list of [[WorkspaceitemSectionUploadFileObject]]
|
||||||
*/
|
*/
|
||||||
|
@@ -2,7 +2,10 @@
|
|||||||
<ds-metadata-field-wrapper *ngIf="bitstreams?.length > 0" [label]="label | translate">
|
<ds-metadata-field-wrapper *ngIf="bitstreams?.length > 0" [label]="label | translate">
|
||||||
<div class="file-section">
|
<div class="file-section">
|
||||||
<ds-themed-file-download-link *ngFor="let file of bitstreams; let last=last;" [bitstream]="file" [item]="item">
|
<ds-themed-file-download-link *ngFor="let file of bitstreams; let last=last;" [bitstream]="file" [item]="item">
|
||||||
<span>{{ dsoNameService.getName(file) }}</span>
|
<span>
|
||||||
|
<span *ngIf="primaryBitsreamId === file.id" class="badge badge-primary">{{ 'item.page.bitstreams.primary' | translate }}</span>
|
||||||
|
{{ dsoNameService.getName(file) }}
|
||||||
|
</span>
|
||||||
<span> ({{(file?.sizeBytes) | dsFileSize }})</span>
|
<span> ({{(file?.sizeBytes) | dsFileSize }})</span>
|
||||||
<span *ngIf="!last" innerHTML="{{separator}}"></span>
|
<span *ngIf="!last" innerHTML="{{separator}}"></span>
|
||||||
</ds-themed-file-download-link>
|
</ds-themed-file-download-link>
|
||||||
|
@@ -25,7 +25,8 @@ describe('FileSectionComponent', () => {
|
|||||||
let fixture: ComponentFixture<FileSectionComponent>;
|
let fixture: ComponentFixture<FileSectionComponent>;
|
||||||
|
|
||||||
const bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', {
|
const bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', {
|
||||||
findAllByItemAndBundleName: createSuccessfulRemoteDataObject$(createPaginatedList([]))
|
findAllByItemAndBundleName: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
|
findPrimaryBitstreamByItemAndName: observableOf(null)
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockBitstream: Bitstream = Object.assign(new Bitstream(),
|
const mockBitstream: Bitstream = Object.assign(new Bitstream(),
|
||||||
@@ -81,6 +82,20 @@ describe('FileSectionComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should set the id of primary bitstream', () => {
|
||||||
|
comp.primaryBitsreamId = undefined;
|
||||||
|
bitstreamDataService.findPrimaryBitstreamByItemAndName.and.returnValue(observableOf(mockBitstream));
|
||||||
|
comp.ngOnInit();
|
||||||
|
expect(comp.primaryBitsreamId).toBe(mockBitstream.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not set the id of primary bitstream', () => {
|
||||||
|
comp.primaryBitsreamId = undefined;
|
||||||
|
bitstreamDataService.findPrimaryBitstreamByItemAndName.and.returnValue(observableOf(null));
|
||||||
|
comp.ngOnInit();
|
||||||
|
expect(comp.primaryBitsreamId).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
describe('when the bitstreams are loading', () => {
|
describe('when the bitstreams are loading', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
comp.bitstreams$.next([mockBitstream]);
|
comp.bitstreams$.next([mockBitstream]);
|
||||||
|
@@ -39,6 +39,8 @@ export class FileSectionComponent implements OnInit {
|
|||||||
|
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
|
||||||
|
primaryBitsreamId: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected bitstreamDataService: BitstreamDataService,
|
protected bitstreamDataService: BitstreamDataService,
|
||||||
protected notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
@@ -50,9 +52,19 @@ export class FileSectionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.getPrimaryBitstreamId();
|
||||||
this.getNextPage();
|
this.getNextPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getPrimaryBitstreamId() {
|
||||||
|
this.bitstreamDataService.findPrimaryBitstreamByItemAndName(this.item, 'ORIGINAL', true, true).subscribe((primaryBitstream: Bitstream | null) => {
|
||||||
|
if (!primaryBitstream) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.primaryBitsreamId = primaryBitstream?.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will retrieve the next page of Bitstreams from the external BitstreamDataService call.
|
* This method will retrieve the next page of Bitstreams from the external BitstreamDataService call.
|
||||||
* It'll retrieve the currentPage from the class variables and it'll add the next page of bitstreams with the
|
* It'll retrieve the currentPage from the class variables and it'll add the next page of bitstreams with the
|
||||||
|
@@ -88,7 +88,7 @@ describe('SearchNavbarComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
it('to search page with empty query', () => {
|
it('to search page with empty query', () => {
|
||||||
const extras: NavigationExtras = { queryParams: { query: '' }, queryParamsHandling: 'merge' };
|
const extras: NavigationExtras = { queryParams: { query: '' } };
|
||||||
expect(component.onSubmit).toHaveBeenCalledWith({ query: '' });
|
expect(component.onSubmit).toHaveBeenCalledWith({ query: '' });
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['search'], extras);
|
expect(router.navigate).toHaveBeenCalledWith(['search'], extras);
|
||||||
});
|
});
|
||||||
@@ -113,7 +113,7 @@ describe('SearchNavbarComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
it('to search page with query', async () => {
|
it('to search page with query', async () => {
|
||||||
const extras: NavigationExtras = { queryParams: { query: 'test' }, queryParamsHandling: 'merge' };
|
const extras: NavigationExtras = { queryParams: { query: 'test' } };
|
||||||
expect(component.onSubmit).toHaveBeenCalledWith({ query: 'test' });
|
expect(component.onSubmit).toHaveBeenCalledWith({ query: 'test' });
|
||||||
|
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['search'], extras);
|
expect(router.navigate).toHaveBeenCalledWith(['search'], extras);
|
||||||
|
@@ -66,8 +66,7 @@ export class SearchNavbarComponent {
|
|||||||
this.searchForm.reset();
|
this.searchForm.reset();
|
||||||
|
|
||||||
this.router.navigate(linkToNavigateTo, {
|
this.router.navigate(linkToNavigateTo, {
|
||||||
queryParams: queryParams,
|
queryParams: queryParams
|
||||||
queryParamsHandling: 'merge'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@ import { SearchConfigurationService } from '../core/shared/search/search-configu
|
|||||||
import { RouteService } from '../core/services/route.service';
|
import { RouteService } from '../core/services/route.service';
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { APP_CONFIG, AppConfig } from '../../config/app-config.interface';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a search page using a configuration as input.
|
* This component renders a search page using a configuration as input.
|
||||||
@@ -32,7 +33,9 @@ export class ConfigurationSearchPageComponent extends SearchComponent {
|
|||||||
protected windowService: HostWindowService,
|
protected windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
protected routeService: RouteService,
|
protected routeService: RouteService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
super(service, sidebarService, windowService, searchConfigService, routeService, router);
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
) {
|
||||||
|
super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,16 +55,21 @@ export class ComcolPageBrowseByComponent implements OnDestroy, OnInit {
|
|||||||
if (this.contentType === 'collection') {
|
if (this.contentType === 'collection') {
|
||||||
comColRoute = getCollectionPageRoute(this.id);
|
comColRoute = getCollectionPageRoute(this.id);
|
||||||
allOptions.push({
|
allOptions.push({
|
||||||
id: 'recent_submissions',
|
id: 'search',
|
||||||
label: 'collection.page.browse.recent.head',
|
label: 'collection.page.browse.search.head',
|
||||||
routerLink: comColRoute,
|
routerLink: comColRoute,
|
||||||
});
|
});
|
||||||
} else if (this.contentType === 'community') {
|
} else if (this.contentType === 'community') {
|
||||||
comColRoute = getCommunityPageRoute(this.id);
|
comColRoute = getCommunityPageRoute(this.id);
|
||||||
|
allOptions.push({
|
||||||
|
id: 'search',
|
||||||
|
label: 'collection.page.browse.search.head',
|
||||||
|
routerLink: comColRoute,
|
||||||
|
});
|
||||||
allOptions.push({
|
allOptions.push({
|
||||||
id: 'comcols',
|
id: 'comcols',
|
||||||
label: 'community.all-lists.head',
|
label: 'community.all-lists.head',
|
||||||
routerLink: comColRoute,
|
routerLink: `${comColRoute}/subcoms-cols`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,8 @@ import { FormModule } from '../form/form.module';
|
|||||||
import { UploadModule } from '../upload/upload.module';
|
import { UploadModule } from '../upload/upload.module';
|
||||||
import { ComcolBrowseByComponent } from './sections/comcol-browse-by/comcol-browse-by.component';
|
import { ComcolBrowseByComponent } from './sections/comcol-browse-by/comcol-browse-by.component';
|
||||||
import { BrowseByModule } from '../../browse-by/browse-by.module';
|
import { BrowseByModule } from '../../browse-by/browse-by.module';
|
||||||
|
import { SearchModule } from '../search/search.module';
|
||||||
|
import { ComcolSearchSectionComponent } from './sections/comcol-search-section/comcol-search-section.component';
|
||||||
|
|
||||||
const COMPONENTS = [
|
const COMPONENTS = [
|
||||||
ComcolPageContentComponent,
|
ComcolPageContentComponent,
|
||||||
@@ -33,6 +35,7 @@ const COMPONENTS = [
|
|||||||
ComcolRoleComponent,
|
ComcolRoleComponent,
|
||||||
ThemedComcolPageHandleComponent,
|
ThemedComcolPageHandleComponent,
|
||||||
ComcolBrowseByComponent,
|
ComcolBrowseByComponent,
|
||||||
|
ComcolSearchSectionComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -45,6 +48,7 @@ const COMPONENTS = [
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
UploadModule,
|
UploadModule,
|
||||||
BrowseByModule,
|
BrowseByModule,
|
||||||
|
SearchModule,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
...COMPONENTS,
|
...COMPONENTS,
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
<ds-themed-search
|
||||||
|
[configuration]="(comcol$ | async)?.type"
|
||||||
|
[showSidebar]="showSidebar$ | async"
|
||||||
|
[showScopeSelector]="false"
|
||||||
|
[hideScopeInUrl]="true"
|
||||||
|
[scope]="(comcol$ | async)?.id">
|
||||||
|
</ds-themed-search>
|
@@ -0,0 +1,35 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ComcolSearchSectionComponent } from './comcol-search-section.component';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ActivatedRouteStub } from '../../../testing/active-router.stub';
|
||||||
|
import { APP_CONFIG } from '../../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../../environments/environment.test';
|
||||||
|
|
||||||
|
describe('ComcolSearchSectionComponent', () => {
|
||||||
|
let component: ComcolSearchSectionComponent;
|
||||||
|
let fixture: ComponentFixture<ComcolSearchSectionComponent>;
|
||||||
|
|
||||||
|
let route: ActivatedRouteStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
route = new ActivatedRouteStub();
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
ComcolSearchSectionComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: ActivatedRoute, useValue: route },
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ComcolSearchSectionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,48 @@
|
|||||||
|
import { Component, OnInit, Inject } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ActivatedRoute, Data } from '@angular/router';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component';
|
||||||
|
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
|
||||||
|
import { hasValue } from '../../../empty.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The search tab on community & collection pages
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-comcol-search-section',
|
||||||
|
templateUrl: './comcol-search-section.component.html',
|
||||||
|
styleUrls: ['./comcol-search-section.component.scss'],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SEARCH_CONFIG_SERVICE,
|
||||||
|
useClass: SearchConfigurationService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ComcolSearchSectionComponent implements OnInit {
|
||||||
|
|
||||||
|
comcol$: Observable<Community | Collection>;
|
||||||
|
|
||||||
|
showSidebar$: Observable<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.comcol$ = this.route.data.pipe(
|
||||||
|
map((data: Data) => (data.dso as RemoteData<Community | Collection>).payload),
|
||||||
|
);
|
||||||
|
this.showSidebar$ = this.comcol$.pipe(
|
||||||
|
map((comcol: Community | Collection) => hasValue(comcol) && this.appConfig[comcol.type as any].searchSection.showSidebar),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
<div [formGroup]="group" class="form-check custom-control custom-switch" [class.disabled]="model.disabled">
|
<div [formGroup]="group" [ngClass]="getClass('element', 'container')" class="form-check custom-control custom-switch" [class.disabled]="model.disabled">
|
||||||
<input type="checkbox" class="form-check-input custom-control-input"
|
<input type="checkbox" class="form-check-input custom-control-input"
|
||||||
[checked]="model.checked"
|
[checked]="model.checked"
|
||||||
[class.is-invalid]="showErrorMessages"
|
[class.is-invalid]="showErrorMessages"
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
(change)="onChange($event)"
|
(change)="onChange($event)"
|
||||||
(focus)="onFocus($event)"/>
|
(focus)="onFocus($event)"/>
|
||||||
<label class="form-check-label custom-control-label" [for]="bindId && model.id">
|
<label class="form-check-label custom-control-label" [for]="bindId && model.id">
|
||||||
<span [innerHTML]="model.label"
|
<span [innerHTML]="model.label | translate"
|
||||||
[ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]"></span>
|
[ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
div.custom-switch {
|
||||||
|
&.custom-control-right {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
right: -1.5rem;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
right: -2.35rem;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
import { DynamicFormsCoreModule, DynamicFormService } from '@ng-dynamic-forms/core';
|
import { DynamicFormsCoreModule, DynamicFormService } from '@ng-dynamic-forms/core';
|
||||||
import { UntypedFormGroup, ReactiveFormsModule } from '@angular/forms';
|
import { UntypedFormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement} from '@angular/core';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { DynamicCustomSwitchModel } from './custom-switch.model';
|
import { DynamicCustomSwitchModel } from './custom-switch.model';
|
||||||
import { CustomSwitchComponent } from './custom-switch.component';
|
import { CustomSwitchComponent } from './custom-switch.component';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
describe('CustomSwitchComponent', () => {
|
describe('CustomSwitchComponent', () => {
|
||||||
|
|
||||||
@@ -20,9 +21,10 @@ describe('CustomSwitchComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
NoopAnimationsModule,
|
NoopAnimationsModule,
|
||||||
DynamicFormsCoreModule.forRoot()
|
DynamicFormsCoreModule.forRoot(),
|
||||||
],
|
],
|
||||||
declarations: [CustomSwitchComponent]
|
declarations: [CustomSwitchComponent]
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ import {
|
|||||||
import { UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormGroup } from '@angular/forms';
|
||||||
|
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { catchError, map, tap } from 'rxjs/operators';
|
import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators';
|
||||||
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
@@ -68,10 +68,14 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.updatePageInfo(this.model.maxOptions, 1);
|
this.updatePageInfo(this.model.maxOptions, 1);
|
||||||
this.loadOptions();
|
this.loadOptions(true);
|
||||||
|
this.group.get(this.model.id).valueChanges.pipe(distinctUntilChanged())
|
||||||
|
.subscribe((value) => {
|
||||||
|
this.setCurrentValue(value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOptions() {
|
loadOptions(fromInit: boolean) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.vocabularyService.getVocabularyEntriesByValue(this.inputText, false, this.model.vocabularyOptions, this.pageInfo).pipe(
|
this.vocabularyService.getVocabularyEntriesByValue(this.inputText, false, this.model.vocabularyOptions, this.pageInfo).pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
@@ -79,6 +83,10 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
tap(() => this.loading = false)
|
tap(() => this.loading = false)
|
||||||
).subscribe((list: PaginatedList<VocabularyEntry>) => {
|
).subscribe((list: PaginatedList<VocabularyEntry>) => {
|
||||||
this.optionsList = list.page;
|
this.optionsList = list.page;
|
||||||
|
if (fromInit && this.model.value) {
|
||||||
|
this.setCurrentValue(this.model.value, true);
|
||||||
|
}
|
||||||
|
|
||||||
this.updatePageInfo(
|
this.updatePageInfo(
|
||||||
list.pageInfo.elementsPerPage,
|
list.pageInfo.elementsPerPage,
|
||||||
list.pageInfo.currentPage,
|
list.pageInfo.currentPage,
|
||||||
@@ -104,7 +112,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
this.group.markAsUntouched();
|
this.group.markAsUntouched();
|
||||||
this.inputText = null;
|
this.inputText = null;
|
||||||
this.updatePageInfo(this.model.maxOptions, 1);
|
this.updatePageInfo(this.model.maxOptions, 1);
|
||||||
this.loadOptions();
|
this.loadOptions(false);
|
||||||
sdRef.open();
|
sdRef.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +169,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
this.inputText += keyName;
|
this.inputText += keyName;
|
||||||
// When a new key is added, we need to reset the page info
|
// When a new key is added, we need to reset the page info
|
||||||
this.updatePageInfo(this.model.maxOptions, 1);
|
this.updatePageInfo(this.model.maxOptions, 1);
|
||||||
this.loadOptions();
|
this.loadOptions(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeKeyFromInput() {
|
removeKeyFromInput() {
|
||||||
@@ -170,7 +178,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
if (this.inputText === '') {
|
if (this.inputText === '') {
|
||||||
this.inputText = null;
|
this.inputText = null;
|
||||||
}
|
}
|
||||||
this.loadOptions();
|
this.loadOptions(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -196,6 +196,7 @@ export class FormBuilderService extends DynamicFormService {
|
|||||||
return new FormFieldMetadataValueObject((controlValue as any).value, controlLanguage, authority, (controlValue as any).display, place, (controlValue as any).confidence);
|
return new FormFieldMetadataValueObject((controlValue as any).value, controlLanguage, authority, (controlValue as any).display, place, (controlValue as any).confidence);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return controlValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
const iterateControlModels = (findGroupModel: DynamicFormControlModel[], controlModelIndex: number = 0): void => {
|
const iterateControlModels = (findGroupModel: DynamicFormControlModel[], controlModelIndex: number = 0): void => {
|
||||||
|
@@ -5,6 +5,9 @@ import { SubmissionFormsConfigDataService } from '../../core/config/submission-f
|
|||||||
*/
|
*/
|
||||||
export function getMockSectionUploadService(): SubmissionFormsConfigDataService {
|
export function getMockSectionUploadService(): SubmissionFormsConfigDataService {
|
||||||
return jasmine.createSpyObj('SectionUploadService', {
|
return jasmine.createSpyObj('SectionUploadService', {
|
||||||
|
updatePrimaryBitstreamOperation: jasmine.createSpy('updatePrimaryBitstreamOperation'),
|
||||||
|
updateFilePrimaryBitstream: jasmine.createSpy('updateFilePrimaryBitstream'),
|
||||||
|
getUploadedFilesData: jasmine.createSpy('getUploadedFilesData'),
|
||||||
getUploadedFileList: jasmine.createSpy('getUploadedFileList'),
|
getUploadedFileList: jasmine.createSpy('getUploadedFileList'),
|
||||||
getFileData: jasmine.createSpy('getFileData'),
|
getFileData: jasmine.createSpy('getFileData'),
|
||||||
getDefaultPolicies: jasmine.createSpy('getDefaultPolicies'),
|
getDefaultPolicies: jasmine.createSpy('getDefaultPolicies'),
|
||||||
|
@@ -1612,7 +1612,13 @@ export const mockUploadFiles = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const mockUploadFilesData = {
|
||||||
|
primary: null,
|
||||||
|
files: JSON.parse(JSON.stringify(mockUploadFiles))
|
||||||
|
};
|
||||||
|
|
||||||
export const mockFileFormData = {
|
export const mockFileFormData = {
|
||||||
|
primary: [true],
|
||||||
metadata: {
|
metadata: {
|
||||||
'dc.title': [
|
'dc.title': [
|
||||||
{
|
{
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Component, EventEmitter, Input, Output, OnChanges } from '@angular/core';
|
import { Component, EventEmitter, Input, Output, OnChanges } from '@angular/core';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { isNotEmpty } from '../empty.util';
|
import { isNotEmpty, hasValue } from '../empty.util';
|
||||||
import { SearchService } from '../../core/shared/search/search.service';
|
import { SearchService } from '../../core/shared/search/search.service';
|
||||||
import { currentPath } from '../utils/route.utils';
|
import { currentPath } from '../utils/route.utils';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
@@ -39,6 +39,11 @@ export class SearchFormComponent implements OnChanges {
|
|||||||
@Input()
|
@Input()
|
||||||
scope = '';
|
scope = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the scope in the url, this can be useful when you hardcode the scope in another way
|
||||||
|
*/
|
||||||
|
@Input() hideScopeInUrl = false;
|
||||||
|
|
||||||
selectedScope: BehaviorSubject<DSpaceObject> = new BehaviorSubject<DSpaceObject>(undefined);
|
selectedScope: BehaviorSubject<DSpaceObject> = new BehaviorSubject<DSpaceObject>(undefined);
|
||||||
|
|
||||||
@Input() currentUrl: string;
|
@Input() currentUrl: string;
|
||||||
@@ -122,6 +127,9 @@ export class SearchFormComponent implements OnChanges {
|
|||||||
},
|
},
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
|
if (hasValue(data.scope) && this.hideScopeInUrl) {
|
||||||
|
delete queryParams.scope;
|
||||||
|
}
|
||||||
|
|
||||||
void this.router.navigate(this.getSearchLinkParts(), {
|
void this.router.navigate(this.getSearchLinkParts(), {
|
||||||
queryParams: queryParams,
|
queryParams: queryParams,
|
||||||
|
@@ -18,6 +18,8 @@ export class ThemedSearchFormComponent extends ThemedComponent<SearchFormCompone
|
|||||||
|
|
||||||
@Input() scope: string;
|
@Input() scope: string;
|
||||||
|
|
||||||
|
@Input() hideScopeInUrl: boolean;
|
||||||
|
|
||||||
@Input() currentUrl: string;
|
@Input() currentUrl: string;
|
||||||
|
|
||||||
@Input() large: boolean;
|
@Input() large: boolean;
|
||||||
@@ -31,7 +33,15 @@ export class ThemedSearchFormComponent extends ThemedComponent<SearchFormCompone
|
|||||||
@Output() submitSearch: EventEmitter<any> = new EventEmitter();
|
@Output() submitSearch: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
protected inAndOutputNames: (keyof SearchFormComponent & keyof this)[] = [
|
protected inAndOutputNames: (keyof SearchFormComponent & keyof this)[] = [
|
||||||
'query', 'inPlaceSearch', 'scope', 'currentUrl', 'large', 'brandColor', 'searchPlaceholder', 'showScopeSelector',
|
'query',
|
||||||
|
'inPlaceSearch',
|
||||||
|
'scope',
|
||||||
|
'hideScopeInUrl',
|
||||||
|
'currentUrl',
|
||||||
|
'large',
|
||||||
|
'brandColor',
|
||||||
|
'searchPlaceholder',
|
||||||
|
'showScopeSelector',
|
||||||
'submitSearch',
|
'submitSearch',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -27,6 +27,8 @@ import { SearchConfigurationServiceStub } from '../../../../testing/search-confi
|
|||||||
import { VocabularyEntryDetail } from '../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
import { VocabularyEntryDetail } from '../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||||
import { FacetValue} from '../../../models/facet-value.model';
|
import { FacetValue} from '../../../models/facet-value.model';
|
||||||
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
||||||
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../../../environments/environment.test';
|
||||||
|
|
||||||
describe('SearchHierarchyFilterComponent', () => {
|
describe('SearchHierarchyFilterComponent', () => {
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ describe('SearchHierarchyFilterComponent', () => {
|
|||||||
let showVocabularyTreeLink: DebugElement;
|
let showVocabularyTreeLink: DebugElement;
|
||||||
|
|
||||||
const testSearchLink = 'test-search';
|
const testSearchLink = 'test-search';
|
||||||
const testSearchFilter = 'test-search-filter';
|
const testSearchFilter = 'subject';
|
||||||
const VocabularyTreeViewComponent = {
|
const VocabularyTreeViewComponent = {
|
||||||
select: new EventEmitter<VocabularyEntryDetail>(),
|
select: new EventEmitter<VocabularyEntryDetail>(),
|
||||||
};
|
};
|
||||||
@@ -73,6 +75,7 @@ describe('SearchHierarchyFilterComponent', () => {
|
|||||||
{ provide: Router, useValue: router },
|
{ provide: Router, useValue: router },
|
||||||
{ provide: NgbModal, useValue: ngbModal },
|
{ provide: NgbModal, useValue: ngbModal },
|
||||||
{ provide: VocabularyService, useValue: vocabularyService },
|
{ provide: VocabularyService, useValue: vocabularyService },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||||
{ provide: IN_PLACE_SEARCH, useValue: false },
|
{ provide: IN_PLACE_SEARCH, useValue: false },
|
||||||
{ provide: FILTER_CONFIG, useValue: Object.assign(new SearchFilterConfig(), { name: testSearchFilter }) },
|
{ provide: FILTER_CONFIG, useValue: Object.assign(new SearchFilterConfig(), { name: testSearchFilter }) },
|
||||||
@@ -86,7 +89,7 @@ describe('SearchHierarchyFilterComponent', () => {
|
|||||||
function init() {
|
function init() {
|
||||||
fixture = TestBed.createComponent(SearchHierarchyFilterComponent);
|
fixture = TestBed.createComponent(SearchHierarchyFilterComponent);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
showVocabularyTreeLink = fixture.debugElement.query(By.css('a#show-test-search-filter-tree'));
|
showVocabularyTreeLink = fixture.debugElement.query(By.css(`a#show-${testSearchFilter}-tree`));
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('if the vocabulary doesn\'t exist', () => {
|
describe('if the vocabulary doesn\'t exist', () => {
|
||||||
|
@@ -24,9 +24,11 @@ import { filter, map, take } from 'rxjs/operators';
|
|||||||
import { VocabularyService } from '../../../../../core/submission/vocabularies/vocabulary.service';
|
import { VocabularyService } from '../../../../../core/submission/vocabularies/vocabulary.service';
|
||||||
import { Observable, BehaviorSubject } from 'rxjs';
|
import { Observable, BehaviorSubject } from 'rxjs';
|
||||||
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
import { environment } from '../../../../../../environments/environment';
|
|
||||||
import { addOperatorToFilterValue } from '../../../search.utils';
|
import { addOperatorToFilterValue } from '../../../search.utils';
|
||||||
import { VocabularyTreeviewModalComponent } from '../../../../form/vocabulary-treeview-modal/vocabulary-treeview-modal.component';
|
import { VocabularyTreeviewModalComponent } from '../../../../form/vocabulary-treeview-modal/vocabulary-treeview-modal.component';
|
||||||
|
import { hasValue } from '../../../../empty.util';
|
||||||
|
import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface';
|
||||||
|
import { FilterVocabularyConfig } from '../../../../../../config/filter-vocabulary-config';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-hierarchy-filter',
|
selector: 'ds-search-hierarchy-filter',
|
||||||
@@ -47,6 +49,7 @@ export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent i
|
|||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected modalService: NgbModal,
|
protected modalService: NgbModal,
|
||||||
protected vocabularyService: VocabularyService,
|
protected vocabularyService: VocabularyService,
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
@Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean,
|
@Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean,
|
||||||
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
||||||
@@ -67,17 +70,20 @@ export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent i
|
|||||||
super.onSubmit(addOperatorToFilterValue(data, 'query'));
|
super.onSubmit(addOperatorToFilterValue(data, 'query'));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.vocabularyExists$ = this.vocabularyService.searchTopEntries(
|
const vocabularyName: string = this.getVocabularyEntry();
|
||||||
this.getVocabularyEntry(), new PageInfo(), true, false,
|
if (hasValue(vocabularyName)) {
|
||||||
).pipe(
|
this.vocabularyExists$ = this.vocabularyService.searchTopEntries(
|
||||||
filter(rd => rd.hasCompleted),
|
vocabularyName, new PageInfo(), true, false,
|
||||||
take(1),
|
).pipe(
|
||||||
map(rd => {
|
filter(rd => rd.hasCompleted),
|
||||||
return rd.hasSucceeded;
|
take(1),
|
||||||
}),
|
map(rd => {
|
||||||
);
|
return rd.hasSucceeded;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,11 +99,11 @@ export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent i
|
|||||||
name: this.getVocabularyEntry(),
|
name: this.getVocabularyEntry(),
|
||||||
closed: true
|
closed: true
|
||||||
};
|
};
|
||||||
modalRef.result.then((detail: VocabularyEntryDetail) => {
|
void modalRef.result.then((detail: VocabularyEntryDetail) => {
|
||||||
this.selectedValues$
|
this.subs.push(this.selectedValues$
|
||||||
.pipe(take(1))
|
.pipe(take(1))
|
||||||
.subscribe((selectedValues) => {
|
.subscribe((selectedValues) => {
|
||||||
this.router.navigate(
|
void this.router.navigate(
|
||||||
[this.searchService.getSearchLink()],
|
[this.searchService.getSearchLink()],
|
||||||
{
|
{
|
||||||
queryParams: {
|
queryParams: {
|
||||||
@@ -107,16 +113,16 @@ export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent i
|
|||||||
queryParamsHandling: 'merge',
|
queryParamsHandling: 'merge',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
}));
|
||||||
}).catch();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the matching vocabulary entry for the given search filter.
|
* Returns the matching vocabulary entry for the given search filter.
|
||||||
* These are configurable in the config file.
|
* These are configurable in the config file.
|
||||||
*/
|
*/
|
||||||
getVocabularyEntry() {
|
getVocabularyEntry(): string {
|
||||||
const foundVocabularyConfig = environment.vocabularies.filter((v) => v.filter === this.filterConfig.name);
|
const foundVocabularyConfig: FilterVocabularyConfig[] = this.appConfig.vocabularies.filter((v: FilterVocabularyConfig) => v.filter === this.filterConfig.name);
|
||||||
if (foundVocabularyConfig.length > 0 && foundVocabularyConfig[0].enabled === true) {
|
if (foundVocabularyConfig.length > 0 && foundVocabularyConfig[0].enabled === true) {
|
||||||
return foundVocabularyConfig[0].vocabulary;
|
return foundVocabularyConfig[0].vocabulary;
|
||||||
}
|
}
|
||||||
|
@@ -85,6 +85,7 @@
|
|||||||
<ds-themed-search-form *ngIf="searchEnabled" id="search-form"
|
<ds-themed-search-form *ngIf="searchEnabled" id="search-form"
|
||||||
[query]="(searchOptions$ | async)?.query"
|
[query]="(searchOptions$ | async)?.query"
|
||||||
[scope]="(searchOptions$ | async)?.scope"
|
[scope]="(searchOptions$ | async)?.scope"
|
||||||
|
[hideScopeInUrl]="hideScopeInUrl"
|
||||||
[currentUrl]="searchLink"
|
[currentUrl]="searchLink"
|
||||||
[showScopeSelector]="showScopeSelector"
|
[showScopeSelector]="showScopeSelector"
|
||||||
[inPlaceSearch]="inPlaceSearch"
|
[inPlaceSearch]="inPlaceSearch"
|
||||||
|
@@ -33,6 +33,8 @@ import { SearchFilterConfig } from './models/search-filter-config.model';
|
|||||||
import { FilterType } from './models/filter-type.model';
|
import { FilterType } from './models/filter-type.model';
|
||||||
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 { environment } from '../../../environments/environment.test';
|
||||||
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
|
||||||
let comp: SearchComponent;
|
let comp: SearchComponent;
|
||||||
let fixture: ComponentFixture<SearchComponent>;
|
let fixture: ComponentFixture<SearchComponent>;
|
||||||
@@ -209,7 +211,8 @@ export function configureSearchComponentTestingModule(compType, additionalDeclar
|
|||||||
{
|
{
|
||||||
provide: SEARCH_CONFIG_SERVICE,
|
provide: SEARCH_CONFIG_SERVICE,
|
||||||
useValue: searchConfigurationServiceStub
|
useValue: searchConfigurationServiceStub
|
||||||
}
|
},
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(compType, {
|
}).overrideComponent(compType, {
|
||||||
|
@@ -31,13 +31,13 @@ import { ViewMode } from '../../core/shared/view-mode.model';
|
|||||||
import { SelectionConfig } from './search-results/search-results.component';
|
import { SelectionConfig } from './search-results/search-results.component';
|
||||||
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
||||||
import { CollectionElementLinkType } from '../object-collection/collection-element-link.type';
|
import { CollectionElementLinkType } from '../object-collection/collection-element-link.type';
|
||||||
import { environment } from 'src/environments/environment';
|
|
||||||
import { SubmissionObject } from '../../core/submission/models/submission-object.model';
|
import { SubmissionObject } from '../../core/submission/models/submission-object.model';
|
||||||
import { SearchFilterConfig } from './models/search-filter-config.model';
|
import { SearchFilterConfig } from './models/search-filter-config.model';
|
||||||
import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model';
|
import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model';
|
||||||
import { ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths';
|
import { ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths';
|
||||||
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
|
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
|
||||||
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
|
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
|
||||||
|
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search',
|
selector: 'ds-search',
|
||||||
@@ -171,6 +171,11 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() scope: string;
|
@Input() scope: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the scope in the url, this can be useful when you hardcode the scope in another way
|
||||||
|
*/
|
||||||
|
@Input() hideScopeInUrl: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current configuration used during the search
|
* The current configuration used during the search
|
||||||
*/
|
*/
|
||||||
@@ -278,7 +283,9 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
protected windowService: HostWindowService,
|
protected windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
protected routeService: RouteService,
|
protected routeService: RouteService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
) {
|
||||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,8 +452,10 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
let followLinks = [
|
let followLinks = [
|
||||||
followLink<Item>('thumbnail', { isOptional: true }),
|
followLink<Item>('thumbnail', { isOptional: true }),
|
||||||
followLink<SubmissionObject>('item', { isOptional: true }, followLink<Item>('thumbnail', { isOptional: true })) as any,
|
followLink<SubmissionObject>('item', { isOptional: true }, followLink<Item>('thumbnail', { isOptional: true })) as any,
|
||||||
followLink<Item>('accessStatus', { isOptional: true, shouldEmbed: environment.item.showAccessStatuses }),
|
|
||||||
];
|
];
|
||||||
|
if (this.appConfig.item.showAccessStatuses) {
|
||||||
|
followLinks.push(followLink<Item>('accessStatus', { isOptional: true }));
|
||||||
|
}
|
||||||
if (this.configuration === 'supervision') {
|
if (this.configuration === 'supervision') {
|
||||||
followLinks.push(followLink<WorkspaceItem>('supervisionOrders', { isOptional: true }) as any);
|
followLinks.push(followLink<WorkspaceItem>('supervisionOrders', { isOptional: true }) as any);
|
||||||
}
|
}
|
||||||
@@ -476,18 +485,20 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
* This method should only be called once and is essentially what SearchTrackingComponent used to do (now removed)
|
* This method should only be called once and is essentially what SearchTrackingComponent used to do (now removed)
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private subscribeToRoutingEvents() {
|
private subscribeToRoutingEvents(): void {
|
||||||
this.subs.push(
|
if (this.trackStatistics) {
|
||||||
this.router.events.pipe(
|
this.subs.push(
|
||||||
filter((event) => event instanceof NavigationStart),
|
this.router.events.pipe(
|
||||||
map((event: NavigationStart) => this.getDsoUUIDFromUrl(event.url)),
|
filter((event) => event instanceof NavigationStart),
|
||||||
hasValueOperator(),
|
map((event: NavigationStart) => this.getDsoUUIDFromUrl(event.url)),
|
||||||
).subscribe((uuid) => {
|
hasValueOperator(),
|
||||||
if (this.resultsRD$.value.hasSucceeded) {
|
).subscribe((uuid) => {
|
||||||
this.service.trackSearch(this.searchOptions$.value, this.resultsRD$.value.payload as SearchObjects<DSpaceObject>, uuid);
|
if (this.resultsRD$.value.hasSucceeded) {
|
||||||
}
|
this.service.trackSearch(this.searchOptions$.value, this.resultsRD$.value.payload as SearchObjects<DSpaceObject>, uuid);
|
||||||
}),
|
}
|
||||||
);
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -43,6 +43,7 @@ export class ThemedSearchComponent extends ThemedComponent<SearchComponent> {
|
|||||||
'trackStatistics',
|
'trackStatistics',
|
||||||
'query',
|
'query',
|
||||||
'scope',
|
'scope',
|
||||||
|
'hideScopeInUrl',
|
||||||
'resultFound',
|
'resultFound',
|
||||||
'deselectObject',
|
'deselectObject',
|
||||||
'selectObject',
|
'selectObject',
|
||||||
@@ -94,6 +95,8 @@ export class ThemedSearchComponent extends ThemedComponent<SearchComponent> {
|
|||||||
|
|
||||||
@Input() scope: string;
|
@Input() scope: string;
|
||||||
|
|
||||||
|
@Input() hideScopeInUrl: boolean;
|
||||||
|
|
||||||
@Output() resultFound: EventEmitter<SearchObjects<DSpaceObject>> = new EventEmitter();
|
@Output() resultFound: EventEmitter<SearchObjects<DSpaceObject>> = new EventEmitter();
|
||||||
|
|
||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
@@ -3,10 +3,12 @@
|
|||||||
<div class="row-with-sidebar row-offcanvas row-offcanvas-left"
|
<div class="row-with-sidebar row-offcanvas row-offcanvas-left"
|
||||||
[@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'">
|
[@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'">
|
||||||
<div id="{{id}}-sidebar-content"
|
<div id="{{id}}-sidebar-content"
|
||||||
|
[class.invisible]="(isSidebarCollapsed$ | async) === true && (isXsOrSm$ | async) === true"
|
||||||
class="col-12 col-md-{{sideBarWidth}} sidebar-content {{sidebarClasses | async}}">
|
class="col-12 col-md-{{sideBarWidth}} sidebar-content {{sidebarClasses | async}}">
|
||||||
<ng-container *ngTemplateOutlet="sidebarContent"></ng-container>
|
<ng-container *ngTemplateOutlet="sidebarContent"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-{{12 - sideBarWidth}}">
|
<div class="col-12 col-md-{{12 - sideBarWidth}}"
|
||||||
|
[class.invisible]="(isSidebarCollapsed$ | async) === false && (isXsOrSm$ | async) === true">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { convertToParamMap, Params } from '@angular/router';
|
import { ActivatedRoute, convertToParamMap, Data, Params } from '@angular/router';
|
||||||
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
export class ActivatedRouteStub {
|
export class ActivatedRouteStub {
|
||||||
|
|
||||||
private _testParams?: any;
|
private _testParams?: Params;
|
||||||
private _testData?: any;
|
private _testData?: Data;
|
||||||
// ActivatedRoute.params is Observable
|
// ActivatedRoute.params is Observable
|
||||||
private subject?: BehaviorSubject<any> = new BehaviorSubject(this.testParams);
|
private subject?: BehaviorSubject<Params> = new BehaviorSubject(this.testParams);
|
||||||
private dataSubject?: BehaviorSubject<any> = new BehaviorSubject(this.testData);
|
private dataSubject?: BehaviorSubject<Data> = new BehaviorSubject(this.testData);
|
||||||
|
|
||||||
params = this.subject.asObservable();
|
params = this.subject.asObservable();
|
||||||
queryParams = this.subject.asObservable();
|
queryParams = this.subject.asObservable();
|
||||||
paramMap = this.subject.asObservable().pipe(map((params: Params) => convertToParamMap(params)));
|
paramMap = this.subject.asObservable().pipe(map((params: Params) => convertToParamMap(params)));
|
||||||
|
parent: ActivatedRoute | ActivatedRouteStub;
|
||||||
queryParamMap = this.subject.asObservable().pipe(map((params: Params) => convertToParamMap(params)));
|
queryParamMap = this.subject.asObservable().pipe(map((params: Params) => convertToParamMap(params)));
|
||||||
data = this.dataSubject.asObservable();
|
data = this.dataSubject.asObservable();
|
||||||
|
|
||||||
@@ -35,17 +35,17 @@ export class ActivatedRouteStub {
|
|||||||
return this._testParams;
|
return this._testParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
set testParams(params: {}) {
|
set testParams(params: Params) {
|
||||||
this._testParams = params;
|
this._testParams = params;
|
||||||
this.subject.next(params);
|
this.subject.next(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test data
|
// Test data
|
||||||
get testData() {
|
get testData() {
|
||||||
return this._testParams;
|
return this._testData;
|
||||||
}
|
}
|
||||||
|
|
||||||
set testData(data: {}) {
|
set testData(data: Data) {
|
||||||
this._testData = data;
|
this._testData = data;
|
||||||
this.dataSubject.next(data);
|
this.dataSubject.next(data);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Angulartics2 } from 'angulartics2';
|
import { Angulartics2, EventTrack } from 'angulartics2';
|
||||||
import { StatisticsService } from '../statistics.service';
|
import { StatisticsService } from '../statistics.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,7 +23,7 @@ export class Angulartics2DSpace {
|
|||||||
.subscribe((event) => this.eventTrack(event));
|
.subscribe((event) => this.eventTrack(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
private eventTrack(event) {
|
private eventTrack(event: Partial<EventTrack>): void {
|
||||||
if (event.action === 'page_view') {
|
if (event.action === 'page_view') {
|
||||||
this.statisticsService.trackViewEvent(event.properties.object, event.properties.referrer);
|
this.statisticsService.trackViewEvent(event.properties.object, event.properties.referrer);
|
||||||
} else if (event.action === 'search') {
|
} else if (event.action === 'search') {
|
||||||
@@ -32,7 +32,7 @@ export class Angulartics2DSpace {
|
|||||||
event.properties.page,
|
event.properties.page,
|
||||||
event.properties.sort,
|
event.properties.sort,
|
||||||
event.properties.filters,
|
event.properties.filters,
|
||||||
event.properties.clickedObject,
|
event.properties.clickedObject?.split('?')[0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -59,6 +59,7 @@ export const SubmissionObjectActionTypes = {
|
|||||||
// Upload file types
|
// Upload file types
|
||||||
NEW_FILE: type('dspace/submission/NEW_FILE'),
|
NEW_FILE: type('dspace/submission/NEW_FILE'),
|
||||||
EDIT_FILE_DATA: type('dspace/submission/EDIT_FILE_DATA'),
|
EDIT_FILE_DATA: type('dspace/submission/EDIT_FILE_DATA'),
|
||||||
|
EDIT_FILE_PRIMARY_BITSTREAM_DATA: type('dspace/submission/EDIT_FILE_PRIMARY_BITSTREAM_DATA'),
|
||||||
DELETE_FILE: type('dspace/submission/DELETE_FILE'),
|
DELETE_FILE: type('dspace/submission/DELETE_FILE'),
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
@@ -760,6 +761,29 @@ export class NewUploadedFileAction implements Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class EditFilePrimaryBitstreamAction implements Action {
|
||||||
|
type = SubmissionObjectActionTypes.EDIT_FILE_PRIMARY_BITSTREAM_DATA;
|
||||||
|
payload: {
|
||||||
|
submissionId: string;
|
||||||
|
sectionId: string;
|
||||||
|
fileId: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit a file data
|
||||||
|
*
|
||||||
|
* @param submissionId
|
||||||
|
* the submission's ID
|
||||||
|
* @param sectionId
|
||||||
|
* the section's ID
|
||||||
|
* @param fileId
|
||||||
|
* the file's ID
|
||||||
|
*/
|
||||||
|
constructor(submissionId: string, sectionId: string, fileId: string | null) {
|
||||||
|
this.payload = { submissionId, sectionId, fileId: fileId };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class EditFileDataAction implements Action {
|
export class EditFileDataAction implements Action {
|
||||||
type = SubmissionObjectActionTypes.EDIT_FILE_DATA;
|
type = SubmissionObjectActionTypes.EDIT_FILE_DATA;
|
||||||
payload: {
|
payload: {
|
||||||
@@ -833,6 +857,7 @@ export type SubmissionObjectAction = DisableSectionAction
|
|||||||
| SectionStatusChangeAction
|
| SectionStatusChangeAction
|
||||||
| NewUploadedFileAction
|
| NewUploadedFileAction
|
||||||
| EditFileDataAction
|
| EditFileDataAction
|
||||||
|
| EditFilePrimaryBitstreamAction
|
||||||
| DeleteUploadedFileAction
|
| DeleteUploadedFileAction
|
||||||
| InertSectionErrorsAction
|
| InertSectionErrorsAction
|
||||||
| DeleteSectionErrorsAction
|
| DeleteSectionErrorsAction
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { hasValue, isEmpty, isNotEmpty, isNotNull, isUndefined } from '../../shared/empty.util';
|
import { hasValue, isEmpty, isNotEmpty, isNotNull, isNull, isUndefined } from '../../shared/empty.util';
|
||||||
import differenceWith from 'lodash/differenceWith';
|
import differenceWith from 'lodash/differenceWith';
|
||||||
import findKey from 'lodash/findKey';
|
import findKey from 'lodash/findKey';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
DepositSubmissionSuccessAction,
|
DepositSubmissionSuccessAction,
|
||||||
DisableSectionAction,
|
DisableSectionAction,
|
||||||
EditFileDataAction,
|
EditFileDataAction,
|
||||||
|
EditFilePrimaryBitstreamAction,
|
||||||
EnableSectionAction,
|
EnableSectionAction,
|
||||||
InertSectionErrorsAction,
|
InertSectionErrorsAction,
|
||||||
InitSectionAction,
|
InitSectionAction,
|
||||||
@@ -203,6 +204,10 @@ export function submissionObjectReducer(state = initialState, action: Submission
|
|||||||
return newFile(state, action as NewUploadedFileAction);
|
return newFile(state, action as NewUploadedFileAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SubmissionObjectActionTypes.EDIT_FILE_PRIMARY_BITSTREAM_DATA: {
|
||||||
|
return editPrimaryBitstream(state, action as EditFilePrimaryBitstreamAction);
|
||||||
|
}
|
||||||
|
|
||||||
case SubmissionObjectActionTypes.EDIT_FILE_DATA: {
|
case SubmissionObjectActionTypes.EDIT_FILE_DATA: {
|
||||||
return editFileData(state, action as EditFileDataAction);
|
return editFileData(state, action as EditFileDataAction);
|
||||||
}
|
}
|
||||||
@@ -735,6 +740,46 @@ function newFile(state: SubmissionObjectState, action: NewUploadedFileAction): S
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit primary bitstream.
|
||||||
|
*
|
||||||
|
* @param state
|
||||||
|
* the current state
|
||||||
|
* @param action
|
||||||
|
* an EditFilePrimaryBitstreamAction action
|
||||||
|
* @return SubmissionObjectState
|
||||||
|
* the new state, with the edited file.
|
||||||
|
*/
|
||||||
|
function editPrimaryBitstream(state: SubmissionObjectState, action: EditFilePrimaryBitstreamAction): SubmissionObjectState {
|
||||||
|
const filesData = state[ action.payload.submissionId ].sections[ action.payload.sectionId ].data as WorkspaceitemSectionUploadObject;
|
||||||
|
const { submissionId, sectionId, fileId } = action.payload;
|
||||||
|
|
||||||
|
const fileIndex = findKey(filesData.files, { uuid: fileId });
|
||||||
|
if (isNull(fileIndex)) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const submission = state[submissionId];
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[submissionId]: {
|
||||||
|
...submission,
|
||||||
|
sections: {
|
||||||
|
...submission.sections,
|
||||||
|
[sectionId]: {
|
||||||
|
...submission.sections[sectionId],
|
||||||
|
data: {
|
||||||
|
...submission.sections[sectionId].data as WorkspaceitemSectionUploadObject,
|
||||||
|
primary: fileId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isLoading: submission.isLoading,
|
||||||
|
savePending: submission.savePending,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit a file.
|
* Edit a file.
|
||||||
*
|
*
|
||||||
|
@@ -213,6 +213,7 @@ export class SubmissionSectionLicenseComponent extends SectionModelComponent {
|
|||||||
} else {
|
} else {
|
||||||
this.operationsBuilder.remove(this.pathCombiner.getPath(path));
|
this.operationsBuilder.remove(this.pathCombiner.getPath(path));
|
||||||
}
|
}
|
||||||
|
this.submissionService.dispatchSaveSection(this.submissionId, this.sectionData.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -6,7 +6,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
||||||
<ds-form *ngIf="formModel"
|
<ds-form *ngIf="formModel"
|
||||||
#formRef="formComponent"
|
#formRef="formComponent"
|
||||||
[formId]="formId"
|
[formId]="formId"
|
||||||
|
@@ -47,6 +47,7 @@ import {
|
|||||||
} from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
} from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
import { dateToISOFormat } from '../../../../../shared/date.util';
|
import { dateToISOFormat } from '../../../../../shared/date.util';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
import { DynamicCustomSwitchModel } from '../../../../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
||||||
|
|
||||||
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
|
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
|
||||||
add: jasmine.createSpy('add'),
|
add: jasmine.createSpy('add'),
|
||||||
@@ -78,7 +79,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);
|
const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId);
|
||||||
|
|
||||||
let noAccessConditionsMock = Object.assign({}, mockFileFormData);
|
let noAccessConditionsMock = Object.assign({}, mockFileFormData);
|
||||||
delete noAccessConditionsMock.accessConditions;
|
delete noAccessConditionsMock.accessConditions;
|
||||||
@@ -186,11 +187,15 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
|
|
||||||
comp.ngOnInit();
|
comp.ngOnInit();
|
||||||
|
|
||||||
|
const models = [DynamicCustomSwitchModel, DynamicFormGroupModel, DynamicFormArrayModel];
|
||||||
|
|
||||||
expect(comp.formModel).toBeDefined();
|
expect(comp.formModel).toBeDefined();
|
||||||
expect(comp.formModel.length).toBe(2);
|
expect(comp.formModel.length).toBe(models.length);
|
||||||
expect(comp.formModel[0] instanceof DynamicFormGroupModel).toBeTruthy();
|
models.forEach((model, i) => {
|
||||||
expect(comp.formModel[1] instanceof DynamicFormArrayModel).toBeTruthy();
|
expect(comp.formModel[i] instanceof model).toBeTruthy();
|
||||||
expect((comp.formModel[1] as DynamicFormArrayModel).groups.length).toBe(2);
|
});
|
||||||
|
|
||||||
|
expect((comp.formModel[2] as DynamicFormArrayModel).groups.length).toBe(2);
|
||||||
const startDateModel = formbuilderService.findById('startDate', comp.formModel);
|
const startDateModel = formbuilderService.findById('startDate', comp.formModel);
|
||||||
expect(startDateModel.max).toEqual(maxStartDate);
|
expect(startDateModel.max).toEqual(maxStartDate);
|
||||||
const endDateModel = formbuilderService.findById('endDate', comp.formModel);
|
const endDateModel = formbuilderService.findById('endDate', comp.formModel);
|
||||||
@@ -254,6 +259,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
compAsAny.formRef = {formGroup: null};
|
compAsAny.formRef = {formGroup: null};
|
||||||
compAsAny.fileData = fileData;
|
compAsAny.fileData = fileData;
|
||||||
compAsAny.pathCombiner = pathCombiner;
|
compAsAny.pathCombiner = pathCombiner;
|
||||||
|
compAsAny.isPrimary = null;
|
||||||
formService.validateAllFormFields.and.callFake(() => null);
|
formService.validateAllFormFields.and.callFake(() => null);
|
||||||
formService.isValid.and.returnValue(of(true));
|
formService.isValid.and.returnValue(of(true));
|
||||||
formService.getFormData.and.returnValue(of(mockFileFormData));
|
formService.getFormData.and.returnValue(of(mockFileFormData));
|
||||||
@@ -262,6 +268,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
Object.assign(mockSubmissionObject, {
|
Object.assign(mockSubmissionObject, {
|
||||||
sections: {
|
sections: {
|
||||||
upload: {
|
upload: {
|
||||||
|
primary: true,
|
||||||
files: mockUploadFiles
|
files: mockUploadFiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,23 +284,28 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
|
|||||||
comp.saveBitstreamData();
|
comp.saveBitstreamData();
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
let path = 'metadata/dc.title';
|
let path = 'primary';
|
||||||
|
expect(uploadService.updatePrimaryBitstreamOperation).toHaveBeenCalledWith(pathCombiner.getPath(path), compAsAny.isPrimary, mockFileFormData.primary[0], compAsAny.fileId);
|
||||||
|
|
||||||
|
const pathFragment = ['files', fileIndex];
|
||||||
|
|
||||||
|
path = 'metadata/dc.title';
|
||||||
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
||||||
pathCombiner.getPath(path),
|
pathCombiner.getPath([...pathFragment, path]),
|
||||||
mockFileFormData.metadata['dc.title'],
|
mockFileFormData.metadata['dc.title'],
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
path = 'metadata/dc.description';
|
path = 'metadata/dc.description';
|
||||||
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
||||||
pathCombiner.getPath(path),
|
pathCombiner.getPath([...pathFragment, path]),
|
||||||
mockFileFormData.metadata['dc.description'],
|
mockFileFormData.metadata['dc.description'],
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
path = 'accessConditions';
|
path = 'accessConditions';
|
||||||
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
expect(operationsBuilder.add).toHaveBeenCalledWith(
|
||||||
pathCombiner.getPath(path),
|
pathCombiner.getPath([...pathFragment, path]),
|
||||||
accessConditionsToSave,
|
accessConditionsToSave,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
@@ -28,6 +28,8 @@ import {
|
|||||||
BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT,
|
BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT,
|
||||||
BITSTREAM_FORM_ACCESS_CONDITION_TYPE_CONFIG,
|
BITSTREAM_FORM_ACCESS_CONDITION_TYPE_CONFIG,
|
||||||
BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT,
|
BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT,
|
||||||
|
BITSTREAM_FORM_PRIMARY,
|
||||||
|
BITSTREAM_FORM_PRIMARY_LAYOUT,
|
||||||
BITSTREAM_METADATA_FORM_GROUP_CONFIG,
|
BITSTREAM_METADATA_FORM_GROUP_CONFIG,
|
||||||
BITSTREAM_METADATA_FORM_GROUP_LAYOUT
|
BITSTREAM_METADATA_FORM_GROUP_LAYOUT
|
||||||
} from './section-upload-file-edit.model';
|
} from './section-upload-file-edit.model';
|
||||||
@@ -42,10 +44,8 @@ import { FormComponent } from '../../../../../shared/form/form.component';
|
|||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { filter, mergeMap, take } from 'rxjs/operators';
|
import { filter, mergeMap, take } from 'rxjs/operators';
|
||||||
import { dateToISOFormat } from '../../../../../shared/date.util';
|
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 { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder';
|
||||||
import {
|
import {
|
||||||
SubmissionJsonPatchOperationsService
|
SubmissionJsonPatchOperationsService
|
||||||
@@ -57,6 +57,9 @@ import { SectionUploadService } from '../../section-upload.service';
|
|||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { DynamicFormControlCondition } from '@ng-dynamic-forms/core/lib/model/misc/dynamic-form-control-relation.model';
|
import { DynamicFormControlCondition } from '@ng-dynamic-forms/core/lib/model/misc/dynamic-form-control-relation.model';
|
||||||
import { DynamicDateControlValue } from '@ng-dynamic-forms/core/lib/model/dynamic-date-control.model';
|
import { DynamicDateControlValue } from '@ng-dynamic-forms/core/lib/model/dynamic-date-control.model';
|
||||||
|
import { DynamicCustomSwitchModel } from 'src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
||||||
|
import { SubmissionObject } from 'src/app/core/submission/models/submission-object.model';
|
||||||
|
import { WorkspaceitemSectionUploadObject } from 'src/app/core/submission/models/workspaceitem-section-upload.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component represents the edit form for bitstream
|
* This component represents the edit form for bitstream
|
||||||
@@ -74,6 +77,13 @@ export class SubmissionSectionUploadFileEditComponent
|
|||||||
*/
|
*/
|
||||||
@ViewChild('formRef') public formRef: FormComponent;
|
@ViewChild('formRef') public formRef: FormComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator is the primary bitstream
|
||||||
|
* it will be null if no primary bitstream is set for the ORIGINAL bundle
|
||||||
|
* @type {boolean, null}
|
||||||
|
*/
|
||||||
|
isPrimary: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of available access condition
|
* The list of available access condition
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
@@ -191,6 +201,10 @@ export class SubmissionSectionUploadFileEditComponent
|
|||||||
* The form model
|
* The form model
|
||||||
*/
|
*/
|
||||||
public initModelData(formModel: DynamicFormControlModel[]) {
|
public initModelData(formModel: DynamicFormControlModel[]) {
|
||||||
|
|
||||||
|
const primaryBitstreamModel: any = this.formBuilderService.findById('primary', formModel, this.fileIndex);
|
||||||
|
primaryBitstreamModel.value = this.isPrimary || false;
|
||||||
|
|
||||||
this.fileData.accessConditions.forEach((accessCondition, index) => {
|
this.fileData.accessConditions.forEach((accessCondition, index) => {
|
||||||
Array.of('name', 'startDate', 'endDate')
|
Array.of('name', 'startDate', 'endDate')
|
||||||
.filter((key) => accessCondition.hasOwnProperty(key) && isNotEmpty(accessCondition[key]))
|
.filter((key) => accessCondition.hasOwnProperty(key) && isNotEmpty(accessCondition[key]))
|
||||||
@@ -291,6 +305,9 @@ export class SubmissionSectionUploadFileEditComponent
|
|||||||
])
|
])
|
||||||
});
|
});
|
||||||
const formModel: DynamicFormControlModel[] = [];
|
const formModel: DynamicFormControlModel[] = [];
|
||||||
|
|
||||||
|
formModel.push(new DynamicCustomSwitchModel(BITSTREAM_FORM_PRIMARY, BITSTREAM_FORM_PRIMARY_LAYOUT));
|
||||||
|
|
||||||
const metadataGroupModelConfig = Object.assign({}, BITSTREAM_METADATA_FORM_GROUP_CONFIG);
|
const metadataGroupModelConfig = Object.assign({}, BITSTREAM_METADATA_FORM_GROUP_CONFIG);
|
||||||
metadataGroupModelConfig.group = this.formBuilderService.modelFromConfiguration(
|
metadataGroupModelConfig.group = this.formBuilderService.modelFromConfiguration(
|
||||||
this.submissionId,
|
this.submissionId,
|
||||||
@@ -386,10 +403,14 @@ export class SubmissionSectionUploadFileEditComponent
|
|||||||
return formModel;
|
return formModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save bitstream metadata
|
* Save bitstream metadata
|
||||||
*/
|
*/
|
||||||
saveBitstreamData() {
|
saveBitstreamData() {
|
||||||
|
|
||||||
|
const pathFragment = ['files', this.fileIndex];
|
||||||
|
|
||||||
// validate form
|
// validate form
|
||||||
this.formService.validateAllFormFields(this.formRef.formGroup);
|
this.formService.validateAllFormFields(this.formRef.formGroup);
|
||||||
const saveBitstreamDataSubscription = this.formService.isValid(this.formId).pipe(
|
const saveBitstreamDataSubscription = this.formService.isValid(this.formId).pipe(
|
||||||
@@ -398,13 +419,15 @@ export class SubmissionSectionUploadFileEditComponent
|
|||||||
mergeMap(() => this.formService.getFormData(this.formId)),
|
mergeMap(() => this.formService.getFormData(this.formId)),
|
||||||
take(1),
|
take(1),
|
||||||
mergeMap((formData: any) => {
|
mergeMap((formData: any) => {
|
||||||
|
this.uploadService.updatePrimaryBitstreamOperation(this.pathCombiner.getPath('primary'), this.isPrimary, formData.primary[0], this.fileId);
|
||||||
|
|
||||||
// collect bitstream metadata
|
// collect bitstream metadata
|
||||||
Object.keys((formData.metadata))
|
Object.keys((formData.metadata))
|
||||||
.filter((key) => isNotEmpty(formData.metadata[key]))
|
.filter((key) => isNotEmpty(formData.metadata[key]))
|
||||||
.forEach((key) => {
|
.forEach((key) => {
|
||||||
const metadataKey = key.replace(/_/g, '.');
|
const metadataKey = key.replace(/_/g, '.');
|
||||||
const path = `metadata/${metadataKey}`;
|
const path = `metadata/${metadataKey}`;
|
||||||
this.operationsBuilder.add(this.pathCombiner.getPath(path), formData.metadata[key], true);
|
this.operationsBuilder.add(this.pathCombiner.getPath([...pathFragment, path]), formData.metadata[key], true);
|
||||||
});
|
});
|
||||||
Object.keys((this.fileData.metadata))
|
Object.keys((this.fileData.metadata))
|
||||||
.filter((key) => isNotEmpty(this.fileData.metadata[key]))
|
.filter((key) => isNotEmpty(this.fileData.metadata[key]))
|
||||||
@@ -413,7 +436,7 @@ export class SubmissionSectionUploadFileEditComponent
|
|||||||
.forEach((key) => {
|
.forEach((key) => {
|
||||||
const metadataKey = key.replace(/_/g, '.');
|
const metadataKey = key.replace(/_/g, '.');
|
||||||
const path = `metadata/${metadataKey}`;
|
const path = `metadata/${metadataKey}`;
|
||||||
this.operationsBuilder.remove(this.pathCombiner.getPath(path));
|
this.operationsBuilder.remove(this.pathCombiner.getPath([...pathFragment, path]));
|
||||||
});
|
});
|
||||||
const accessConditionsToSave = [];
|
const accessConditionsToSave = [];
|
||||||
if (formData.hasOwnProperty('accessConditions')) {
|
if (formData.hasOwnProperty('accessConditions')) {
|
||||||
@@ -469,25 +492,29 @@ export class SubmissionSectionUploadFileEditComponent
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isNotEmpty(accessConditionsToSave)) {
|
if (isNotEmpty(accessConditionsToSave)) {
|
||||||
this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true);
|
this.operationsBuilder.add(this.pathCombiner.getPath([...pathFragment, 'accessConditions']), accessConditionsToSave, true);
|
||||||
}
|
}
|
||||||
|
// dispatch a PATCH request to save metadata
|
||||||
// dispatch a PATCH request to save metadata
|
return this.operationsService.jsonPatchByResourceID(
|
||||||
return this.operationsService.jsonPatchByResourceID(
|
this.submissionService.getSubmissionObjectLinkName(),
|
||||||
this.submissionService.getSubmissionObjectLinkName(),
|
this.submissionId,
|
||||||
this.submissionId,
|
this.pathCombiner.rootElement,
|
||||||
this.pathCombiner.rootElement,
|
this.pathCombiner.subRootElement);
|
||||||
this.pathCombiner.subRootElement);
|
})
|
||||||
})
|
|
||||||
).subscribe((result: SubmissionObject[]) => {
|
).subscribe((result: SubmissionObject[]) => {
|
||||||
if (result[0].sections[this.sectionId]) {
|
const section = result[0].sections[this.sectionId];
|
||||||
const uploadSection = (result[0].sections[this.sectionId] as WorkspaceitemSectionUploadObject);
|
if (!section) {
|
||||||
Object.keys(uploadSection.files)
|
return;
|
||||||
.filter((key) => uploadSection.files[key].uuid === this.fileId)
|
|
||||||
.forEach((key) => this.uploadService.updateFileData(
|
|
||||||
this.submissionId, this.sectionId, this.fileId, uploadSection.files[key])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
const uploadSection = (section as WorkspaceitemSectionUploadObject);
|
||||||
|
|
||||||
|
this.uploadService.updateFilePrimaryBitstream(this.submissionId, this.sectionId, uploadSection.primary);
|
||||||
|
|
||||||
|
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.isSaving = false;
|
||||||
this.activeModal.close();
|
this.activeModal.close();
|
||||||
});
|
});
|
||||||
|
@@ -4,6 +4,7 @@ import {
|
|||||||
DynamicFormControlLayout,
|
DynamicFormControlLayout,
|
||||||
DynamicFormGroupModelConfig,
|
DynamicFormGroupModelConfig,
|
||||||
DynamicSelectModelConfig,
|
DynamicSelectModelConfig,
|
||||||
|
DynamicSwitchModelConfig,
|
||||||
MATCH_ENABLED,
|
MATCH_ENABLED,
|
||||||
OR_OPERATOR,
|
OR_OPERATOR,
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
@@ -56,6 +57,19 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT: DynamicFormControlLayo
|
|||||||
label: 'col-form-label name-label'
|
label: 'col-form-label name-label'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
export const BITSTREAM_FORM_PRIMARY_LAYOUT: DynamicFormControlLayout = {
|
||||||
|
element: {
|
||||||
|
host: 'col-12',
|
||||||
|
container: 'text-right'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BITSTREAM_FORM_PRIMARY: DynamicSwitchModelConfig = {
|
||||||
|
id: 'primary',
|
||||||
|
name: 'primary',
|
||||||
|
label: 'bitstream.edit.form.primaryBitstream.label'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG: DynamicDatePickerModelConfig = {
|
export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG: DynamicDatePickerModelConfig = {
|
||||||
id: 'startDate',
|
id: 'startDate',
|
||||||
|
@@ -1,6 +1,22 @@
|
|||||||
<ng-container *ngIf="fileData">
|
<ng-container *ngIf="fileData">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<!-- Default switch -->
|
||||||
|
<div class="col-md-2 d-flex justify-content-center align-items-center" >
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="custom-control-input"
|
||||||
|
id="primaryBitstream{{fileIndex}}"
|
||||||
|
[disabled]="processingSaveStatus$ | async"
|
||||||
|
[checked]="isPrimary"
|
||||||
|
(change)="togglePrimaryBitstream($event)">
|
||||||
|
<label class="custom-control-label" for="primaryBitstream{{fileIndex}}">
|
||||||
|
<span class="sr-only" *ngIf="!isPrimary">{{'submission.sections.upload.primary.make' | translate:{ fileName: fileName } }}</span>
|
||||||
|
<span class="sr-only" *ngIf="isPrimary">{{'submission.sections.upload.primary.remove' | translate:{ fileName: fileName } }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10">
|
||||||
<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>
|
||||||
|
@@ -66,7 +66,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
|
|||||||
const fileName = '123456-test-upload.jpg';
|
const fileName = '123456-test-upload.jpg';
|
||||||
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);
|
const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId);
|
||||||
|
|
||||||
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
|
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
|
||||||
add: jasmine.createSpy('add'),
|
add: jasmine.createSpy('add'),
|
||||||
@@ -201,6 +201,23 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should delete primary if file we delete is primary', () => {
|
||||||
|
compAsAny.isPrimary = true;
|
||||||
|
compAsAny.pathCombiner = pathCombiner;
|
||||||
|
operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
|
||||||
|
compAsAny.deleteFile();
|
||||||
|
expect(operationsBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath('primary'));
|
||||||
|
expect(uploadService.updateFilePrimaryBitstream).toHaveBeenCalledWith(submissionId, sectionId, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT delete primary if file we delete is NOT primary', () => {
|
||||||
|
compAsAny.isPrimary = false;
|
||||||
|
compAsAny.pathCombiner = pathCombiner;
|
||||||
|
operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
|
||||||
|
compAsAny.deleteFile();
|
||||||
|
expect(uploadService.updateFilePrimaryBitstream).not.toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('should delete file properly', () => {
|
it('should delete file properly', () => {
|
||||||
compAsAny.pathCombiner = pathCombiner;
|
compAsAny.pathCombiner = pathCombiner;
|
||||||
operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
|
operationsService.jsonPatchByResourceID.and.returnValue(observableOf({}));
|
||||||
@@ -209,7 +226,8 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
|
|||||||
compAsAny.deleteFile();
|
compAsAny.deleteFile();
|
||||||
|
|
||||||
expect(uploadService.removeUploadedFile).toHaveBeenCalledWith(submissionId, sectionId, fileId);
|
expect(uploadService.removeUploadedFile).toHaveBeenCalledWith(submissionId, sectionId, fileId);
|
||||||
expect(operationsBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath());
|
expect(operationsBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath(['files', fileIndex]));
|
||||||
|
|
||||||
expect(operationsService.jsonPatchByResourceID).toHaveBeenCalledWith(
|
expect(operationsService.jsonPatchByResourceID).toHaveBeenCalledWith(
|
||||||
'workspaceitems',
|
'workspaceitems',
|
||||||
submissionId,
|
submissionId,
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
Component,
|
||||||
Input,
|
Input,
|
||||||
OnChanges,
|
OnChanges,
|
||||||
@@ -9,7 +8,7 @@ import {
|
|||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import { filter } 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';
|
||||||
@@ -22,7 +21,6 @@ import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/buil
|
|||||||
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 { SubmissionService } from '../../../submission.service';
|
import { SubmissionService } from '../../../submission.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 { 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';
|
||||||
@@ -37,6 +35,12 @@ import { NgbModalOptions } from '@ng-bootstrap/ng-bootstrap/modal/modal-config';
|
|||||||
templateUrl: './section-upload-file.component.html',
|
templateUrl: './section-upload-file.component.html',
|
||||||
})
|
})
|
||||||
export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit, OnDestroy {
|
export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit, OnDestroy {
|
||||||
|
/**
|
||||||
|
* The indicator is the primary bitstream
|
||||||
|
* it will be null if no primary bitstream is set for the ORIGINAL bundle
|
||||||
|
* @type {boolean, null}
|
||||||
|
*/
|
||||||
|
@Input() isPrimary: boolean | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of available access condition
|
* The list of available access condition
|
||||||
@@ -100,6 +104,11 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
|||||||
*/
|
*/
|
||||||
@ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent;
|
@ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if a submission save operation is pending
|
||||||
|
* @type {Observable<boolean>}
|
||||||
|
*/
|
||||||
|
public processingSaveStatus$: Observable<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The bitstream's metadata data
|
* The bitstream's metadata data
|
||||||
@@ -137,6 +146,12 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
|||||||
*/
|
*/
|
||||||
protected pathCombiner: JsonPatchOperationPathCombiner;
|
protected pathCombiner: JsonPatchOperationPathCombiner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [JsonPatchOperationPathCombiner] object
|
||||||
|
* @type {JsonPatchOperationPathCombiner}
|
||||||
|
*/
|
||||||
|
protected primaryBitstreamPathCombiner: JsonPatchOperationPathCombiner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
@@ -162,9 +177,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
|||||||
* @param {SectionUploadService} uploadService
|
* @param {SectionUploadService} uploadService
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private cdr: ChangeDetectorRef,
|
|
||||||
private formService: FormService,
|
private formService: FormService,
|
||||||
private halService: HALEndpointService,
|
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private operationsBuilder: JsonPatchOperationsBuilder,
|
private operationsBuilder: JsonPatchOperationsBuilder,
|
||||||
private operationsService: SubmissionJsonPatchOperationsService,
|
private operationsService: SubmissionJsonPatchOperationsService,
|
||||||
@@ -197,7 +210,8 @@ 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.processingSaveStatus$ = this.submissionService.getSubmissionSaveProcessingStatus(this.submissionId);
|
||||||
|
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId);
|
||||||
this.loadFormMetadata();
|
this.loadFormMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +261,12 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
|||||||
activeModal.componentInstance.formMetadata = this.formMetadata;
|
activeModal.componentInstance.formMetadata = this.formMetadata;
|
||||||
activeModal.componentInstance.pathCombiner = this.pathCombiner;
|
activeModal.componentInstance.pathCombiner = this.pathCombiner;
|
||||||
activeModal.componentInstance.submissionId = this.submissionId;
|
activeModal.componentInstance.submissionId = this.submissionId;
|
||||||
|
activeModal.componentInstance.isPrimary = this.isPrimary;
|
||||||
|
}
|
||||||
|
|
||||||
|
togglePrimaryBitstream(event) {
|
||||||
|
this.uploadService.updatePrimaryBitstreamOperation(this.pathCombiner.getPath('primary'), this.isPrimary, event.target.checked, this.fileId);
|
||||||
|
this.submissionService.dispatchSaveSection(this.submissionId, this.sectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@@ -273,13 +292,20 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit,
|
|||||||
* Delete bitstream from submission
|
* Delete bitstream from submission
|
||||||
*/
|
*/
|
||||||
protected deleteFile() {
|
protected deleteFile() {
|
||||||
this.operationsBuilder.remove(this.pathCombiner.getPath());
|
this.operationsBuilder.remove(this.pathCombiner.getPath(['files', this.fileIndex]));
|
||||||
|
if (this.isPrimary) {
|
||||||
|
this.operationsBuilder.remove(this.pathCombiner.getPath('primary'));
|
||||||
|
}
|
||||||
|
|
||||||
this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
|
this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
|
||||||
this.submissionService.getSubmissionObjectLinkName(),
|
this.submissionService.getSubmissionObjectLinkName(),
|
||||||
this.submissionId,
|
this.submissionId,
|
||||||
this.pathCombiner.rootElement,
|
this.pathCombiner.rootElement,
|
||||||
this.pathCombiner.subRootElement)
|
this.pathCombiner.subRootElement)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
|
if (this.isPrimary) {
|
||||||
|
this.uploadService.updateFilePrimaryBitstream(this.submissionId, this.sectionId, null);
|
||||||
|
}
|
||||||
this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId);
|
this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId);
|
||||||
this.processingDelete$.next(false);
|
this.processingDelete$.next(false);
|
||||||
}));
|
}));
|
||||||
|
@@ -17,6 +17,13 @@ export class ThemedSubmissionSectionUploadFileComponent
|
|||||||
*/
|
*/
|
||||||
@Input() availableAccessConditionOptions: any[];
|
@Input() availableAccessConditionOptions: any[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator is the primary bitstream
|
||||||
|
* it will be null if no primary bitstream is set for the ORIGINAL bundle
|
||||||
|
* @type {boolean, null}
|
||||||
|
*/
|
||||||
|
@Input() isPrimary: boolean | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The submission id
|
* The submission id
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@@ -69,6 +76,7 @@ export class ThemedSubmissionSectionUploadFileComponent
|
|||||||
|
|
||||||
protected inAndOutputNames: (keyof SubmissionSectionUploadFileComponent & keyof this)[] = [
|
protected inAndOutputNames: (keyof SubmissionSectionUploadFileComponent & keyof this)[] = [
|
||||||
'availableAccessConditionOptions',
|
'availableAccessConditionOptions',
|
||||||
|
'isPrimary',
|
||||||
'collectionId',
|
'collectionId',
|
||||||
'collectionPolicyType',
|
'collectionPolicyType',
|
||||||
'configMetadataForm',
|
'configMetadataForm',
|
||||||
|
@@ -2,15 +2,7 @@
|
|||||||
[dismissible]="true"
|
[dismissible]="true"
|
||||||
[type]="AlertTypeEnum.Info"></ds-alert>
|
[type]="AlertTypeEnum.Info"></ds-alert>
|
||||||
|
|
||||||
<ng-container *ngIf="fileList.length == 0">
|
<ng-container *ngIf="fileList.length > 0; else noFileUploaded">
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<h3 class="text-center"><span class="text-muted">{{'submission.sections.upload.no-file-uploaded' | translate}}</span></h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngIf="fileList.length > 0">
|
|
||||||
|
|
||||||
<div *ngIf="collectionDefaultAccessConditions.length > 0" class="row">
|
<div *ngIf="collectionDefaultAccessConditions.length > 0" class="row">
|
||||||
<div class="col-sm-12" >
|
<div class="col-sm-12" >
|
||||||
@@ -26,16 +18,26 @@
|
|||||||
</ds-alert>
|
</ds-alert>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
<ng-container *ngFor="let fileEntry of fileList">
|
<div class="col-md-2">
|
||||||
|
<span class="text-left font-weight-bold">{{ 'bitstream.edit.form.primaryBitstream.label' | translate }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngFor="let fileEntry of fileList; let i = index;">
|
||||||
<ds-themed-submission-upload-section-file
|
<ds-themed-submission-upload-section-file
|
||||||
|
[isPrimary]="primaryBitstreamUUID ? primaryBitstreamUUID === fileEntry.uuid : null"
|
||||||
[availableAccessConditionOptions]="availableAccessConditionOptions"
|
[availableAccessConditionOptions]="availableAccessConditionOptions"
|
||||||
[collectionId]="collectionId"
|
[collectionId]="collectionId"
|
||||||
[collectionPolicyType]="collectionPolicyType"
|
[collectionPolicyType]="collectionPolicyType"
|
||||||
[configMetadataForm]="(configMetadataForm$ | async)"
|
[configMetadataForm]="(configMetadataForm$ | async)"
|
||||||
[fileId]="fileIndexes[fileList.indexOf(fileEntry)]"
|
[fileId]="fileEntry.uuid"
|
||||||
[fileIndex]="fileList.indexOf(fileEntry)"
|
[fileIndex]="i"
|
||||||
[fileName]="fileNames[fileList.indexOf(fileEntry)]"
|
[fileName]="fileNames[i]"
|
||||||
[sectionId]="sectionData.id"
|
[sectionId]="sectionData.id"
|
||||||
[submissionId]="submissionId"></ds-themed-submission-upload-section-file>
|
[submissionId]="submissionId"></ds-themed-submission-upload-section-file>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -45,3 +47,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #noFileUploaded>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3 class="text-center"><span class="text-muted">{{'submission.sections.upload.no-file-uploaded' | translate}}</span></h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
@@ -25,6 +25,7 @@ import {
|
|||||||
mockUploadConfigResponse,
|
mockUploadConfigResponse,
|
||||||
mockUploadConfigResponseNotRequired,
|
mockUploadConfigResponseNotRequired,
|
||||||
mockUploadFiles,
|
mockUploadFiles,
|
||||||
|
mockUploadFilesData,
|
||||||
} from '../../../shared/mocks/submission.mock';
|
} from '../../../shared/mocks/submission.mock';
|
||||||
import { SubmissionUploadsConfigDataService } from '../../../core/config/submission-uploads-config-data.service';
|
import { SubmissionUploadsConfigDataService } from '../../../core/config/submission-uploads-config-data.service';
|
||||||
import { SectionUploadService } from './section-upload.service';
|
import { SectionUploadService } from './section-upload.service';
|
||||||
@@ -160,6 +161,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
bitstreamService.getUploadedFileList.and.returnValue(observableOf([]));
|
bitstreamService.getUploadedFileList.and.returnValue(observableOf([]));
|
||||||
|
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
|
||||||
};
|
};
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -230,7 +232,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should init component properly', () => {
|
it('should init component properly', () => {
|
||||||
|
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
|
||||||
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
||||||
|
|
||||||
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(Object.assign(new Collection(), mockCollection, {
|
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(Object.assign(new Collection(), mockCollection, {
|
||||||
@@ -246,15 +248,8 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
|||||||
createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup))
|
createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup))
|
||||||
);
|
);
|
||||||
|
|
||||||
bitstreamService.getUploadedFileList.and.returnValue(observableOf([]));
|
|
||||||
|
|
||||||
comp.onSectionInit();
|
comp.onSectionInit();
|
||||||
|
|
||||||
const expectedGroupsMap = new Map([
|
|
||||||
[mockUploadConfigResponse.accessConditionOptions[1].name, [mockGroup as any]],
|
|
||||||
[mockUploadConfigResponse.accessConditionOptions[2].name, [mockGroup as any]],
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(comp.collectionId).toBe(collectionId);
|
expect(comp.collectionId).toBe(collectionId);
|
||||||
expect(comp.collectionName).toBe(mockCollection.name);
|
expect(comp.collectionName).toBe(mockCollection.name);
|
||||||
expect(comp.availableAccessConditionOptions.length).toBe(4);
|
expect(comp.availableAccessConditionOptions.length).toBe(4);
|
||||||
@@ -262,12 +257,12 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
|||||||
expect(comp.required$.getValue()).toBe(true);
|
expect(comp.required$.getValue()).toBe(true);
|
||||||
expect(compAsAny.subs.length).toBe(2);
|
expect(compAsAny.subs.length).toBe(2);
|
||||||
expect(compAsAny.fileList).toEqual([]);
|
expect(compAsAny.fileList).toEqual([]);
|
||||||
expect(compAsAny.fileIndexes).toEqual([]);
|
|
||||||
expect(compAsAny.fileNames).toEqual([]);
|
expect(compAsAny.fileNames).toEqual([]);
|
||||||
|
expect(compAsAny.primaryBitstreamUUID).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should init file list properly', () => {
|
it('should init file list properly', () => {
|
||||||
|
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
|
||||||
|
|
||||||
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
||||||
|
|
||||||
@@ -282,7 +277,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
|||||||
createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup))
|
createSuccessfulRemoteDataObject$(Object.assign(new Group(), mockGroup))
|
||||||
);
|
);
|
||||||
|
|
||||||
bitstreamService.getUploadedFileList.and.returnValue(observableOf(mockUploadFiles));
|
bitstreamService.getUploadedFilesData.and.returnValue(observableOf(mockUploadFilesData));
|
||||||
|
|
||||||
comp.onSectionInit();
|
comp.onSectionInit();
|
||||||
|
|
||||||
@@ -298,12 +293,14 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
|||||||
expect(comp.required$.getValue()).toBe(true);
|
expect(comp.required$.getValue()).toBe(true);
|
||||||
expect(compAsAny.subs.length).toBe(2);
|
expect(compAsAny.subs.length).toBe(2);
|
||||||
expect(compAsAny.fileList).toEqual(mockUploadFiles);
|
expect(compAsAny.fileList).toEqual(mockUploadFiles);
|
||||||
expect(compAsAny.fileIndexes).toEqual(['123456-test-upload']);
|
expect(compAsAny.primaryBitstreamUUID).toEqual(null);
|
||||||
expect(compAsAny.fileNames).toEqual(['123456-test-upload.jpg']);
|
expect(compAsAny.fileNames).toEqual(['123456-test-upload.jpg']);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should properly read the section status when required is true', () => {
|
it('should properly read the section status when required is true', () => {
|
||||||
|
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
|
||||||
|
|
||||||
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
||||||
|
|
||||||
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
|
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
|
||||||
@@ -324,7 +321,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
|||||||
|
|
||||||
comp.onSectionInit();
|
comp.onSectionInit();
|
||||||
|
|
||||||
expect(comp.required$.getValue()).toBe(true);
|
expect(comp.required$.getValue()).toBe(true);
|
||||||
|
|
||||||
expect(compAsAny.getSectionStatus()).toBeObservable(cold('-c-d', {
|
expect(compAsAny.getSectionStatus()).toBeObservable(cold('-c-d', {
|
||||||
c: false,
|
c: false,
|
||||||
@@ -335,6 +332,7 @@ describe('SubmissionSectionUploadComponent test suite', () => {
|
|||||||
it('should properly read the section status when required is false', () => {
|
it('should properly read the section status when required is false', () => {
|
||||||
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
submissionServiceStub.getSubmissionObject.and.returnValue(observableOf(submissionState));
|
||||||
|
|
||||||
|
bitstreamService.getUploadedFilesData.and.returnValue(observableOf({ primary: null, files: [] }));
|
||||||
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
|
collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
|
||||||
|
|
||||||
resourcePolicyService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(mockDefaultAccessCondition));
|
resourcePolicyService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(mockDefaultAccessCondition));
|
||||||
|
@@ -4,7 +4,8 @@ import {
|
|||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
Subscription
|
Subscription,
|
||||||
|
combineLatest
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ import { AccessConditionOption } from '../../../core/config/models/config-access
|
|||||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||||
import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
|
import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
|
||||||
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { WorkspaceitemSectionUploadObject } from 'src/app/core/submission/models/workspaceitem-section-upload.model';
|
||||||
|
|
||||||
export const POLICY_DEFAULT_NO_LIST = 1; // Banner1
|
export const POLICY_DEFAULT_NO_LIST = 1; // Banner1
|
||||||
export const POLICY_DEFAULT_WITH_LIST = 2; // Banner2
|
export const POLICY_DEFAULT_WITH_LIST = 2; // Banner2
|
||||||
@@ -58,10 +60,10 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent {
|
|||||||
public AlertTypeEnum = AlertType;
|
public AlertTypeEnum = AlertType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The array containing the keys of file list array
|
* The uuid of primary bitstream file
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
*/
|
*/
|
||||||
public fileIndexes: string[] = [];
|
public primaryBitstreamUUID: string | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The file list
|
* The file list
|
||||||
@@ -194,27 +196,18 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent {
|
|||||||
this.changeDetectorRef.detectChanges();
|
this.changeDetectorRef.detectChanges();
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// retrieve submission's bitstreams from state
|
|
||||||
observableCombineLatest(this.configMetadataForm$,
|
// retrieve submission's bitstream data from state
|
||||||
this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id)).pipe(
|
combineLatest([this.configMetadataForm$,
|
||||||
filter(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => {
|
this.bitstreamService.getUploadedFilesData(this.submissionId, this.sectionData.id)]).pipe(
|
||||||
return isNotEmpty(configMetadataForm) && isNotUndefined(fileList);
|
filter(([configMetadataForm, { files }]: [SubmissionFormsModel, WorkspaceitemSectionUploadObject]) => {
|
||||||
|
return isNotEmpty(configMetadataForm) && isNotEmpty(files);
|
||||||
}),
|
}),
|
||||||
distinctUntilChanged())
|
distinctUntilChanged())
|
||||||
.subscribe(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => {
|
.subscribe(([configMetadataForm, { primary, files }]: [SubmissionFormsModel, WorkspaceitemSectionUploadObject]) => {
|
||||||
this.fileList = [];
|
this.primaryBitstreamUUID = primary;
|
||||||
this.fileIndexes = [];
|
this.fileList = files;
|
||||||
this.fileNames = [];
|
this.fileNames = Array.from(files, file => this.getFileName(configMetadataForm, file));
|
||||||
this.changeDetectorRef.detectChanges();
|
|
||||||
if (isNotUndefined(fileList) && fileList.length > 0) {
|
|
||||||
fileList.forEach((file) => {
|
|
||||||
this.fileList.push(file);
|
|
||||||
this.fileIndexes.push(file.uuid);
|
|
||||||
this.fileNames.push(this.getFileName(configMetadataForm, file));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.detectChanges();
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@@ -0,0 +1,63 @@
|
|||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { JsonPatchOperationPathCombiner } from 'src/app/core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
|
import { JsonPatchOperationsBuilder } from 'src/app/core/json-patch/builder/json-patch-operations-builder';
|
||||||
|
import { SectionUploadService } from './section-upload.service';
|
||||||
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
|
|
||||||
|
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
|
||||||
|
add: jasmine.createSpy('add'),
|
||||||
|
replace: jasmine.createSpy('replace'),
|
||||||
|
remove: jasmine.createSpy('remove'),
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SectionUploadService test suite', () => {
|
||||||
|
let sectionUploadService: SectionUploadService;
|
||||||
|
let operationsBuilder: any;
|
||||||
|
const pathCombiner = new JsonPatchOperationPathCombiner('sections', 'upload');
|
||||||
|
const primaryPath = pathCombiner.getPath('primary');
|
||||||
|
const fileId = 'test';
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [StoreModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: Store, useValue: {} },
|
||||||
|
SectionUploadService,
|
||||||
|
{ provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sectionUploadService = TestBed.inject(SectionUploadService);
|
||||||
|
operationsBuilder = TestBed.inject(JsonPatchOperationsBuilder);
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
initialPrimary: null,
|
||||||
|
primary: true,
|
||||||
|
operationName: 'add',
|
||||||
|
expected: [primaryPath, fileId, false, true]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initialPrimary: true,
|
||||||
|
primary: false,
|
||||||
|
operationName: 'remove',
|
||||||
|
expected: [primaryPath]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initialPrimary: false,
|
||||||
|
primary: true,
|
||||||
|
operationName: 'replace',
|
||||||
|
expected: [primaryPath, fileId, true]
|
||||||
|
}
|
||||||
|
].forEach(({ initialPrimary, primary, operationName, expected }) => {
|
||||||
|
it(`updatePrimaryBitstreamOperation should add ${operationName} operation`, () => {
|
||||||
|
const path = pathCombiner.getPath('primary');
|
||||||
|
sectionUploadService.updatePrimaryBitstreamOperation(path, initialPrimary, primary, fileId);
|
||||||
|
expect(operationsBuilder[operationName]).toHaveBeenCalledWith(...expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -8,11 +8,15 @@ import { SubmissionState } from '../../submission.reducers';
|
|||||||
import {
|
import {
|
||||||
DeleteUploadedFileAction,
|
DeleteUploadedFileAction,
|
||||||
EditFileDataAction,
|
EditFileDataAction,
|
||||||
|
EditFilePrimaryBitstreamAction,
|
||||||
NewUploadedFileAction
|
NewUploadedFileAction
|
||||||
} from '../../objects/submission-objects.actions';
|
} from '../../objects/submission-objects.actions';
|
||||||
import { submissionUploadedFileFromUuidSelector, submissionUploadedFilesFromIdSelector } from '../../selectors';
|
import { submissionSectionDataFromIdSelector, submissionUploadedFileFromUuidSelector, submissionUploadedFilesFromIdSelector } from '../../selectors';
|
||||||
import { isUndefined } from '../../../shared/empty.util';
|
import { isUndefined } from '../../../shared/empty.util';
|
||||||
import { WorkspaceitemSectionUploadFileObject } from '../../../core/submission/models/workspaceitem-section-upload-file.model';
|
import { WorkspaceitemSectionUploadFileObject } from '../../../core/submission/models/workspaceitem-section-upload-file.model';
|
||||||
|
import { WorkspaceitemSectionUploadObject } from 'src/app/core/submission/models/workspaceitem-section-upload.model';
|
||||||
|
import { JsonPatchOperationPathObject } from 'src/app/core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
|
import { JsonPatchOperationsBuilder } from 'src/app/core/json-patch/builder/json-patch-operations-builder';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service that provides methods to handle submission's bitstream state.
|
* A service that provides methods to handle submission's bitstream state.
|
||||||
@@ -24,8 +28,53 @@ export class SectionUploadService {
|
|||||||
* Initialize service variables
|
* Initialize service variables
|
||||||
*
|
*
|
||||||
* @param {Store<SubmissionState>} store
|
* @param {Store<SubmissionState>} store
|
||||||
|
* @param {JsonPatchOperationsBuilder} operationsBuilder
|
||||||
*/
|
*/
|
||||||
constructor(private store: Store<SubmissionState>) {}
|
constructor(private store: Store<SubmissionState>, private operationsBuilder: JsonPatchOperationsBuilder) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define and add an operation based on a change
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* The path to endpoint
|
||||||
|
* @param intitialPrimary
|
||||||
|
* The initial primary indicator
|
||||||
|
* @param primary
|
||||||
|
* the new primary indicator
|
||||||
|
* @param fileId
|
||||||
|
* The file id
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
public updatePrimaryBitstreamOperation(path: JsonPatchOperationPathObject, intitialPrimary: boolean | null, primary: boolean | null, fileId: string): void {
|
||||||
|
if (intitialPrimary === null && primary) {
|
||||||
|
this.operationsBuilder.add(path, fileId, false, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intitialPrimary !== primary) {
|
||||||
|
if (primary) {
|
||||||
|
this.operationsBuilder.replace(path, fileId, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.operationsBuilder.remove(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return submission's bitstream data from state
|
||||||
|
*
|
||||||
|
* @param submissionId
|
||||||
|
* The submission id
|
||||||
|
* @param sectionId
|
||||||
|
* The section id
|
||||||
|
* @returns {WorkspaceitemSectionUploadObject}
|
||||||
|
* Returns submission's bitstream data
|
||||||
|
*/
|
||||||
|
public getUploadedFilesData(submissionId: string, sectionId: string): Observable<WorkspaceitemSectionUploadObject> {
|
||||||
|
return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)).pipe(
|
||||||
|
map((state) => state),
|
||||||
|
distinctUntilChanged());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return submission's bitstream list from state
|
* Return submission's bitstream list from state
|
||||||
@@ -104,6 +153,22 @@ export class SectionUploadService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update primary bitstream into the state
|
||||||
|
*
|
||||||
|
* @param submissionId
|
||||||
|
* The submission id
|
||||||
|
* @param sectionId
|
||||||
|
* The section id
|
||||||
|
* @param fileUUID
|
||||||
|
* The bitstream UUID
|
||||||
|
*/
|
||||||
|
public updateFilePrimaryBitstream(submissionId: string, sectionId: string, fileUUID: string | null) {
|
||||||
|
this.store.dispatch(
|
||||||
|
new EditFilePrimaryBitstreamAction(submissionId, sectionId, fileUUID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update bitstream metadata into the state
|
* Update bitstream metadata into the state
|
||||||
*
|
*
|
||||||
|
@@ -11,7 +11,7 @@ import { hasValue, isNotEmpty } from '../shared/empty.util';
|
|||||||
import { ResearcherProfile } from '../core/profile/model/researcher-profile.model';
|
import { ResearcherProfile } from '../core/profile/model/researcher-profile.model';
|
||||||
import {
|
import {
|
||||||
getAllSucceededRemoteDataPayload,
|
getAllSucceededRemoteDataPayload,
|
||||||
getFinishedRemoteData,
|
getFinishedRemoteData, getFirstCompletedRemoteData,
|
||||||
getFirstSucceededRemoteDataPayload,
|
getFirstSucceededRemoteDataPayload,
|
||||||
getFirstSucceededRemoteListPayload
|
getFirstSucceededRemoteListPayload
|
||||||
} from '../core/shared/operators';
|
} from '../core/shared/operators';
|
||||||
@@ -155,10 +155,10 @@ export class SuggestionsService {
|
|||||||
*/
|
*/
|
||||||
public retrieveCurrentUserSuggestions(userUuid: string): Observable<SuggestionTarget[]> {
|
public retrieveCurrentUserSuggestions(userUuid: string): Observable<SuggestionTarget[]> {
|
||||||
return this.researcherProfileService.findById(userUuid, true).pipe(
|
return this.researcherProfileService.findById(userUuid, true).pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstCompletedRemoteData(),
|
||||||
mergeMap((profile: ResearcherProfile) => {
|
mergeMap((profile: RemoteData<ResearcherProfile> ) => {
|
||||||
if (isNotEmpty(profile)) {
|
if (isNotEmpty(profile) && profile.hasSucceeded && isNotEmpty(profile.payload)) {
|
||||||
return this.researcherProfileService.findRelatedItemId(profile).pipe(
|
return this.researcherProfileService.findRelatedItemId(profile.payload).pipe(
|
||||||
mergeMap((itemId: string) => {
|
mergeMap((itemId: string) => {
|
||||||
return this.suggestionsDataService.getTargetsByUser(itemId).pipe(
|
return this.suggestionsDataService.getTargetsByUser(itemId).pipe(
|
||||||
getFirstSucceededRemoteListPayload()
|
getFirstSucceededRemoteListPayload()
|
||||||
@@ -169,7 +169,7 @@ export class SuggestionsService {
|
|||||||
return of([]);
|
return of([]);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
take(1)
|
catchError(() => of([]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -726,7 +726,7 @@
|
|||||||
|
|
||||||
"bitstream.edit.form.newFormat.hint": "The application you used to create the file, and the version number (for example, \"<i>ACMESoft SuperApp version 1.5</i>\").",
|
"bitstream.edit.form.newFormat.hint": "The application you used to create the file, and the version number (for example, \"<i>ACMESoft SuperApp version 1.5</i>\").",
|
||||||
|
|
||||||
"bitstream.edit.form.primaryBitstream.label": "Primary bitstream",
|
"bitstream.edit.form.primaryBitstream.label": "Primary File",
|
||||||
|
|
||||||
"bitstream.edit.form.selectedFormat.hint": "If the format is not in the above list, <b>select \"format not in list\" above</b> and describe it under \"Describe new format\".",
|
"bitstream.edit.form.selectedFormat.hint": "If the format is not in the above list, <b>select \"format not in list\" above</b> and describe it under \"Describe new format\".",
|
||||||
|
|
||||||
@@ -1108,9 +1108,7 @@
|
|||||||
|
|
||||||
"collection.logo": "Collection logo",
|
"collection.logo": "Collection logo",
|
||||||
|
|
||||||
"collection.page.browse.recent.head": "Recent Submissions",
|
"collection.page.browse.search.head": "Search",
|
||||||
|
|
||||||
"collection.page.browse.recent.empty": "No items to show",
|
|
||||||
|
|
||||||
"collection.page.edit": "Edit this collection",
|
"collection.page.edit": "Edit this collection",
|
||||||
|
|
||||||
@@ -1120,6 +1118,8 @@
|
|||||||
|
|
||||||
"collection.page.news": "News",
|
"collection.page.news": "News",
|
||||||
|
|
||||||
|
"collection.search.results.head": "Search Results",
|
||||||
|
|
||||||
"collection.select.confirm": "Confirm selected",
|
"collection.select.confirm": "Confirm selected",
|
||||||
|
|
||||||
"collection.select.empty": "No collections to show",
|
"collection.select.empty": "No collections to show",
|
||||||
@@ -1196,6 +1196,8 @@
|
|||||||
|
|
||||||
"community.browse.logo": "Browse for a community logo",
|
"community.browse.logo": "Browse for a community logo",
|
||||||
|
|
||||||
|
"community.subcoms-cols.breadcrumbs": "Subcommunities and Collections",
|
||||||
|
|
||||||
"community.create.head": "Create a Community",
|
"community.create.head": "Create a Community",
|
||||||
|
|
||||||
"community.create.notifications.success": "Successfully created the Community",
|
"community.create.notifications.success": "Successfully created the Community",
|
||||||
@@ -1348,6 +1350,8 @@
|
|||||||
|
|
||||||
"community.all-lists.head": "Subcommunities and Collections",
|
"community.all-lists.head": "Subcommunities and Collections",
|
||||||
|
|
||||||
|
"community.search.results.head": "Search Results",
|
||||||
|
|
||||||
"community.sub-collection-list.head": "Collections in this Community",
|
"community.sub-collection-list.head": "Collections in this Community",
|
||||||
|
|
||||||
"community.sub-community-list.head": "Communities in this Community",
|
"community.sub-community-list.head": "Communities in this Community",
|
||||||
@@ -2496,6 +2500,8 @@
|
|||||||
|
|
||||||
"item.page.bitstreams.collapse": "Collapse",
|
"item.page.bitstreams.collapse": "Collapse",
|
||||||
|
|
||||||
|
"item.page.bitstreams.primary": "Primary",
|
||||||
|
|
||||||
"item.page.filesection.original.bundle": "Original bundle",
|
"item.page.filesection.original.bundle": "Original bundle",
|
||||||
|
|
||||||
"item.page.filesection.license.bundle": "License bundle",
|
"item.page.filesection.license.bundle": "License bundle",
|
||||||
@@ -4370,6 +4376,8 @@
|
|||||||
|
|
||||||
"submission.import-external.source.datacite": "DataCite",
|
"submission.import-external.source.datacite": "DataCite",
|
||||||
|
|
||||||
|
"submission.import-external.source.doi": "DOI",
|
||||||
|
|
||||||
"submission.import-external.source.scielo": "SciELO",
|
"submission.import-external.source.scielo": "SciELO",
|
||||||
|
|
||||||
"submission.import-external.source.scopus": "Scopus",
|
"submission.import-external.source.scopus": "Scopus",
|
||||||
@@ -4838,6 +4846,10 @@
|
|||||||
|
|
||||||
"submission.sections.toggle.aria.close": "Collapse {{sectionHeader}} section",
|
"submission.sections.toggle.aria.close": "Collapse {{sectionHeader}} section",
|
||||||
|
|
||||||
|
"submission.sections.upload.primary.make": "Make {{fileName}} the primary bitstream",
|
||||||
|
|
||||||
|
"submission.sections.upload.primary.remove": "Remove {{fileName}} as the primary bitstream",
|
||||||
|
|
||||||
"submission.sections.upload.delete.confirm.cancel": "Cancel",
|
"submission.sections.upload.delete.confirm.cancel": "Cancel",
|
||||||
|
|
||||||
"submission.sections.upload.delete.confirm.info": "This operation can't be undone. Are you sure?",
|
"submission.sections.upload.delete.confirm.info": "This operation can't be undone. Are you sure?",
|
||||||
|
@@ -8,6 +8,7 @@ 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 { ItemConfig } from './item-config.interface';
|
import { ItemConfig } from './item-config.interface';
|
||||||
|
import { CommunityPageConfig } from './community-page-config.interface';
|
||||||
import { CollectionPageConfig } from './collection-page-config.interface';
|
import { CollectionPageConfig } from './collection-page-config.interface';
|
||||||
import { ThemeConfig } from './theme.config';
|
import { ThemeConfig } from './theme.config';
|
||||||
import { AuthConfig } from './auth-config.interfaces';
|
import { AuthConfig } from './auth-config.interfaces';
|
||||||
@@ -41,6 +42,7 @@ interface AppConfig extends Config {
|
|||||||
communityList: CommunityListConfig;
|
communityList: CommunityListConfig;
|
||||||
homePage: HomeConfig;
|
homePage: HomeConfig;
|
||||||
item: ItemConfig;
|
item: ItemConfig;
|
||||||
|
community: CommunityPageConfig;
|
||||||
collection: CollectionPageConfig;
|
collection: CollectionPageConfig;
|
||||||
themes: ThemeConfig[];
|
themes: ThemeConfig[];
|
||||||
mediaViewer: MediaViewerConfig;
|
mediaViewer: MediaViewerConfig;
|
||||||
|
@@ -1,7 +1,18 @@
|
|||||||
import { Config } from './config.interface';
|
import { Config } from './config.interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection Page Config
|
||||||
|
*/
|
||||||
export interface CollectionPageConfig extends Config {
|
export interface CollectionPageConfig extends Config {
|
||||||
|
searchSection: CollectionSearchSectionConfig;
|
||||||
edit: {
|
edit: {
|
||||||
undoTimeout: number;
|
undoTimeout: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config related to the collection's search tab
|
||||||
|
*/
|
||||||
|
export interface CollectionSearchSectionConfig {
|
||||||
|
showSidebar: boolean;
|
||||||
|
}
|
||||||
|
15
src/config/community-page-config.interface.ts
Normal file
15
src/config/community-page-config.interface.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Config } from './config.interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Community Page Config
|
||||||
|
*/
|
||||||
|
export interface CommunityPageConfig extends Config {
|
||||||
|
searchSection: CommunitySearchSectionConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config related to the community's search tab
|
||||||
|
*/
|
||||||
|
export interface CommunitySearchSectionConfig {
|
||||||
|
showSidebar: boolean;
|
||||||
|
}
|
@@ -23,6 +23,7 @@ import { HomeConfig } from './homepage-config.interface';
|
|||||||
import { MarkdownConfig } from './markdown-config.interface';
|
import { MarkdownConfig } from './markdown-config.interface';
|
||||||
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
||||||
import { DiscoverySortConfig } from './discovery-sort.config';
|
import { DiscoverySortConfig } from './discovery-sort.config';
|
||||||
|
import { CommunityPageConfig } from './community-page-config.interface';
|
||||||
import {QualityAssuranceConfig} from './quality-assurance.config';
|
import {QualityAssuranceConfig} from './quality-assurance.config';
|
||||||
|
|
||||||
export class DefaultAppConfig implements AppConfig {
|
export class DefaultAppConfig implements AppConfig {
|
||||||
@@ -291,8 +292,18 @@ export class DefaultAppConfig implements AppConfig {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Community Page Config
|
||||||
|
community: CommunityPageConfig = {
|
||||||
|
searchSection: {
|
||||||
|
showSidebar: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Collection Page Config
|
// Collection Page Config
|
||||||
collection: CollectionPageConfig = {
|
collection: CollectionPageConfig = {
|
||||||
|
searchSection: {
|
||||||
|
showSidebar: true,
|
||||||
|
},
|
||||||
edit: {
|
edit: {
|
||||||
undoTimeout: 10000 // 10 seconds
|
undoTimeout: 10000 // 10 seconds
|
||||||
}
|
}
|
||||||
|
@@ -257,7 +257,15 @@ export const environment: BuildConfig = {
|
|||||||
pageSize: 5
|
pageSize: 5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
community: {
|
||||||
|
searchSection: {
|
||||||
|
showSidebar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
collection: {
|
collection: {
|
||||||
|
searchSection: {
|
||||||
|
showSidebar: true,
|
||||||
|
},
|
||||||
edit: {
|
edit: {
|
||||||
undoTimeout: 10000 // 10 seconds
|
undoTimeout: 10000 // 10 seconds
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user