62849: progress july 5

This commit is contained in:
lotte
2019-07-05 15:19:16 +02:00
parent 998a7107a8
commit 5c39d3c8d1
13 changed files with 243 additions and 52 deletions

View File

@@ -13,7 +13,6 @@ import { combineLatest as combineLatestObservable } from 'rxjs';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model'; import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model';
import { CreateCommunityParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component'; import { CreateCommunityParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component';
import { CreateItemParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component'; import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component';
import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component';
import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component'; import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component';

View File

@@ -44,7 +44,6 @@ export abstract class BaseResponseParsingService {
} }
}); });
} }
this.cache(object, requestUUID); this.cache(object, requestUUID);
return object; return object;
} }

View File

@@ -10,7 +10,7 @@ import {
getRemoteDataPayload, getResponseFromEntry, getRemoteDataPayload, getResponseFromEntry,
getSucceededRemoteData getSucceededRemoteData
} from '../shared/operators'; } from '../shared/operators';
import { DeleteRequest, RestRequest } from './request.models'; import { DeleteRequest, PostRequest, RestRequest } from './request.models';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RestResponse } from '../cache/response.models'; import { RestResponse } from '../cache/response.models';
import { Item } from '../shared/item.model'; import { Item } from '../shared/item.model';
@@ -64,6 +64,22 @@ export class RelationshipService {
); );
} }
/**
* Send a post request for a relationship by ID
* @param item1
* @param item2
*/
addRelationship(item1: Item, item2: Item): Observable<RestResponse> {
return this.halService.getEndpoint(this.linkPath).pipe(
isNotEmptyOperator(),
distinctUntilChanged(),
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL, `${item1.self} ${item2.self}`)),
configureRequest(this.requestService),
switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)),
getResponseFromEntry()
);
}
/** /**
* Get a combined observable containing an array of all relationships in an item, as well as an array of the relationships their types * Get a combined observable containing an array of all relationships in an item, as well as an array of the relationships their types
* This is used for easier access of a relationship's type because they exist as observables * This is used for easier access of a relationship's type because they exist as observables

View File

@@ -10,7 +10,6 @@ import { BrowseDefinition } from './browse-definition.model';
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
import { PaginatedList } from '../data/paginated-list'; import { PaginatedList } from '../data/paginated-list';
import { SearchResult } from '../../+search-page/search-result.model'; import { SearchResult } from '../../+search-page/search-result.model';
import { Item } from './item.model';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
/** /**

View File

@@ -72,7 +72,6 @@ import { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_RELATION } from './models/lookup-relat
import { DsDynamicLookupRelationComponent } from './models/lookup-relation/dynamic-lookup-relation.component'; import { DsDynamicLookupRelationComponent } from './models/lookup-relation/dynamic-lookup-relation.component';
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null { export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
console.log(model.type);
switch (model.type) { switch (model.type) {
case DYNAMIC_FORM_CONTROL_TYPE_ARRAY: case DYNAMIC_FORM_CONTROL_TYPE_ARRAY:
return DsDynamicFormArrayComponent; return DsDynamicFormArrayComponent;

View File

@@ -1,26 +1,88 @@
<div class="container"> <div class="modal-header">
<div class="search-page row"> <h4 class="modal-title" id="modal-title">Choose a {{fieldName}}</h4>
<div class="col-12"> <button type="button" class="close" aria-label="Close button" aria-describedby="modal-title"
<div class="row"> (click)="modal.dismiss('Cross click')">
<div id="search-body"> <span aria-hidden="true">&times;</span>
<div id="search-content" class="col-12" *ngVar="(resultsRD$ | async) as resultsRD"> </button>
<ds-pagination </div>
[paginationOptions]="searchConfig.pagination" <div class="modal-body" *ngVar="(resultsRD$ | async) as resultsRD">
[collectionSize]="resultsRD?.payload?.totalElements" <ds-loading *ngIf="!resultsRD || resultsRD.isLoading"></ds-loading>
[sortOptions]="searchConfig.sort" <div *ngIf="resultsRD.hasSucceeded">
[hideGear]="true" <div *ngIf="repeatable">
[hidePagerWhenSinglePage]="true" <div class="input-group mb-3">
(paginationChange)="onPaginationChange($event)"> <div class="input-group-prepend">
<ul class="lookup-results list-unstyled"> <div class="input-group-text">
<li *ngFor="let result of resultsRD?.payload?.page; let i = index" class="my-4 d-flex"> <!-- In theory we don't need separate checkboxes for this,
<input type="checkbox" [value]="result.indexableObject.uuid"/> but I wasn't able to get this to work correctly without them.
<ds-wrapper-list-element class="result-list-element" [object]="result" [index]="i"></ds-wrapper-list-element> Checkboxes that are in the indeterminate state always switch to checked when clicked
</li> This seemed like the cleanest and clearest solution to solve this issue for now.
</ul> -->
</ds-pagination> <input *ngIf="!isAllSelected() && !isSomeSelected()" type="checkbox"
[indeterminate]="false"
(change)="selectAll()"
>
<input *ngIf="!isAllSelected() && isSomeSelected()" type="checkbox"
[indeterminate]="true"
(change)="deselectAll()"
>
<input *ngIf="isAllSelected()" type="checkbox"
[checked]="true"
(change)="deselectAll()"
>
</div>
</div>
<div ngbDropdown class="input-group-append">
<button id="resultdropdown" type="button" ngbDropdownToggle
class="btn btn-outline-secondary dropdown-toggle-split"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div ngbDropdownMenu aria-labelledby="resultdropdown">
<button class="dropdown-item"
(click)="selectPage(resultsRD?.payload?.page)">Select page
</button>
<button class="dropdown-item"
(click)="deselectPage(resultsRD?.payload?.page)">Deselect
page
</button>
<button class="dropdown-item" (click)="selectAll()">Select all</button>
<button class="dropdown-item" (click)="deselectAll()">Deselect all</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<ds-pagination
[paginationOptions]="searchConfig.pagination"
[collectionSize]="resultsRD?.payload?.totalElements"
[sortOptions]="searchConfig.sort"
[hideGear]="true"
[hidePagerWhenSinglePage]="true"
(paginationChange)="onPaginationChange($event.pagination)">
<div class="form-check" *ngFor="let result of resultsRD?.payload?.page; let i = index">
<input *ngIf="repeatable" class="form-check-input" type="checkbox"
[name]="'checkbox' + i"
[id]="'object'+i"
[checked]="isSelected(result.indexableObject)"
(change)="selectCheckbox($event.currentTarget.checked, result.indexableObject)">
<input *ngIf="!repeatable" class="form-check-input" type="radio"
[name]="'radio' + i"
[id]="'object'+i"
[checked]="isSelected(result.indexableObject)"
(change)="selectRadio($event.currentTarget.checked, result.indexableObject)">
<label class="form-check-label" [for]="'object'+i">
<ds-wrapper-list-element class="result-list-element"
[object]="result"
[index]="i"></ds-wrapper-list-element>
</label>
</div>
</ds-pagination>
</div>
</div>
<div class="modal-footer">
<small>Selected {{selection.length}} items</small>
<div>
<button type="button" class="btn btn-outline-secondary" (click)="modal.dismiss()">Cancel
</button>
<button type="button" class="btn btn-danger" (click)="close()">Ok</button>
</div> </div>
</div> </div>

View File

@@ -1,3 +1,7 @@
.result-list-element { .result-list-element {
flex: 1; flex: 1;
}
.modal-footer {
justify-content: space-between;
} }

View File

@@ -2,11 +2,16 @@ import { Component, OnInit } from '@angular/core';
import { PaginatedList } from '../../../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../../../core/data/paginated-list';
import { SearchResult } from '../../../../../../+search-page/search-result.model'; import { SearchResult } from '../../../../../../+search-page/search-result.model';
import { RemoteData } from '../../../../../../core/data/remote-data'; import { RemoteData } from '../../../../../../core/data/remote-data';
import { Observable } from 'rxjs'; import { asyncScheduler, Observable, ReplaySubject } from 'rxjs';
import { SearchService } from '../../../../../../+search-page/search-service/search.service'; import { SearchService } from '../../../../../../+search-page/search-service/search.service';
import { PaginatedSearchOptions } from '../../../../../../+search-page/paginated-search-options.model'; import { PaginatedSearchOptions } from '../../../../../../+search-page/paginated-search-options.model';
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { hasNoValue, hasValue, isNotEmpty } from '../../../../../empty.util';
import { getSucceededRemoteData } from '../../../../../../core/shared/operators';
import { finalize, map, takeUntil, takeWhile } from 'rxjs/operators';
import { concat, multicast, take } from 'rxjs/operators';
const RELATION_TYPE_FILTER_PREFIX = 'f.entityType='; const RELATION_TYPE_FILTER_PREFIX = 'f.entityType=';
@@ -20,20 +25,111 @@ const RELATION_TYPE_METADATA_PREFIX = 'relation.isPublicationOf';
}) })
export class DsDynamicLookupRelationModalComponent implements OnInit { export class DsDynamicLookupRelationModalComponent implements OnInit {
relationKey: string; relationKey: string;
fieldName: string;
resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>; resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>;
searchConfig: PaginatedSearchOptions; searchConfig: PaginatedSearchOptions;
repeatable: boolean = true;
selection: DSpaceObject[] = [];
allSelected = false;
constructor(private searchService: SearchService) { constructor(protected modal: NgbActiveModal, private searchService: SearchService) {
} }
ngOnInit(): void { ngOnInit(): void {
const pagination = Object.assign(new PaginationComponentOptions(), { pageSize: 5 }); this.fieldName = this.relationKey.substring(RELATION_TYPE_METADATA_PREFIX.length);
this.searchConfig = new PaginatedSearchOptions({ const pagination = Object.assign(new PaginationComponentOptions(), {
pagination: pagination, id: 'submission-relation-list',
fixedFilter: RELATION_TYPE_FILTER_PREFIX + this.relationKey.substring(RELATION_TYPE_METADATA_PREFIX.length) pageSize: 5
}); });
this.resultsRD$ = this.searchService.search(this.searchConfig); this.onPaginationChange(pagination);
} }
onPaginationChange() {} onPaginationChange(pagination: PaginationComponentOptions) {
this.searchConfig = new PaginatedSearchOptions({
pagination: pagination,
fixedFilter: RELATION_TYPE_FILTER_PREFIX + this.fieldName
});
this.resultsRD$ = this.searchService.search(this.searchConfig).pipe(
/* Make sure to only listen to the first x results, until loading is finished */
/* TODO: in Rxjs 6.4.0 and up, we can replace this by takeWhile(predicate, true) - see https://stackoverflow.com/a/44644237 */
multicast(
() => new ReplaySubject(1),
subject => subject.pipe(
takeWhile((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => rd.isLoading),
concat(subject.pipe(take(1))
)
)
) as any
)
}
close() {
this.modal.close(this.selection);
}
isSelected(dso: DSpaceObject): boolean {
return hasValue(this.selection.find((selected) => selected.uuid === dso.uuid));
}
selectCheckbox(value: boolean, dso: DSpaceObject) {
if (value) {
this.selection = [...this.selection, dso];
} else {
this.allSelected = false;
this.selection = this.selection.filter((selected) => {
return selected.uuid !== dso.uuid
});
}
}
selectRadio(value: boolean, dso: DSpaceObject) {
if (value) {
this.selection = [dso];
}
}
selectPage(page: SearchResult<DSpaceObject>[]) {
const newObjects: DSpaceObject[] = page
.map((searchResult) => searchResult.indexableObject)
.filter((dso) => hasNoValue(this.selection.find((selected) => selected.uuid === dso.uuid)));
this.selection = [...this.selection, ...newObjects]
}
deselectPage(page: SearchResult<DSpaceObject>[]) {
this.allSelected = false;
const objects: DSpaceObject[] = page
.map((searchResult) => searchResult.indexableObject);
this.selection = this.selection.filter((selected) => hasNoValue(objects.find((object) => object.uuid === selected.uuid)));
}
selectAll() {
this.allSelected = true;
const fullPagination = Object.assign(new PaginationComponentOptions(), {
currentPage: 1,
pageSize: Number.POSITIVE_INFINITY
});
const fullSearchConfig = Object.assign(this.searchConfig, { pagination: fullPagination });
const results = this.searchService.search(fullSearchConfig);
results.pipe(
getSucceededRemoteData(),
map((resultsRD) => resultsRD.payload.page)
)
.subscribe((results) =>
this.selection = [...this.selection, ...results.map((searchResult) => searchResult.indexableObject)]
);
}
deselectAll() {
this.allSelected = false;
this.selection = [];
}
isAllSelected() {
return this.allSelected;
}
isSomeSelected() {
return isNotEmpty(this.selection);
}
} }

View File

@@ -3,15 +3,20 @@
<!--Simple lookup, first field --> <!--Simple lookup, first field -->
<div class="col right-addon"> <div class="col right-addon">
<input class="form-control" <input class="form-control"
[attr.autoComplete]="model.autoComplete"
[class.is-invalid]="showErrorMessages" [class.is-invalid]="showErrorMessages"
[dynamicId]="bindId && model.id" [value]="modalValuesString"
[name]="model.name"
[type]="model.inputType"
[(ngModel)]="model.value"
[disabled]="model.disabled" [disabled]="model.disabled"
[type]="model.inputType"
[placeholder]="model.placeholder | translate" [placeholder]="model.placeholder | translate"
[readonly]="model.readOnly"> [readonly]="model.readOnly">
<ng-container *ngFor="let value of model.value">
<input [dynamicId]="bindId && model.id"
[name]="model.name"
type="hidden"
[ngModel]="value.uuid"
[disabled]="true"
[readonly]="true">
</ng-container>
</div> </div>
<div class="col-auto text-center"> <div class="col-auto text-center">
<button class="btn btn-secondary" <button class="btn btn-secondary"

View File

@@ -6,10 +6,12 @@ import {
DynamicFormValidationService DynamicFormValidationService
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { DynamicRelationGroupModel } from '../relation-group/dynamic-relation-group.model';
import { isNotEmpty } from '../../../../../empty.util'; import { isNotEmpty } from '../../../../../empty.util';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { DsDynamicLookupRelationModalComponent } from './dynamic-lookup-relation-modal.component'; import { DsDynamicLookupRelationModalComponent } from './dynamic-lookup-relation-modal.component';
import { DynamicLookupRelationModel } from './dynamic-lookup-relation.model';
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
import { RelationshipService } from '../../../../../../core/data/relationship.service';
@Component({ @Component({
selector: 'ds-dynamic-lookup-relation', selector: 'ds-dynamic-lookup-relation',
@@ -20,22 +22,26 @@ export class DsDynamicLookupRelationComponent extends DynamicFormControlComponen
@Input() formId: string; @Input() formId: string;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicRelationGroupModel; @Input() model: DynamicLookupRelationModel;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>();
@Output() focus: EventEmitter<any> = new EventEmitter<any>(); @Output() focus: EventEmitter<any> = new EventEmitter<any>();
modalRef: NgbModalRef; modalRef: NgbModalRef;
modalValuesString = '';
currentObjects;
constructor(private modalService: NgbModal, constructor(private modalService: NgbModal,
protected layoutService: DynamicFormLayoutService, protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService protected validationService: DynamicFormValidationService,
private relationshipService: RelationshipService
) { ) {
super(layoutService, validationService); super(layoutService, validationService);
} }
ngOnInit(): void { ngOnInit(): void {
this.currentObjects = this.relationshipService.getItemRelationshipLabels();
} }
public hasEmptyValue() { public hasEmptyValue() {
@@ -45,8 +51,9 @@ export class DsDynamicLookupRelationComponent extends DynamicFormControlComponen
openLookup() { openLookup() {
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent); this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent);
this.modalRef.componentInstance.relationKey = this.model.name; this.modalRef.componentInstance.relationKey = this.model.name;
this.modalRef.result.then((result) => { this.modalRef.result.then((resultList) => {
this.model.value = result; this.model.value = resultList;
this.modalValuesString = resultList.map((dso: DSpaceObject) => dso.name).join('; ');
}); });
} }

View File

@@ -10,7 +10,7 @@ export interface DynamicLookupRelationModelConfig extends DsDynamicInputModelCon
export class DynamicLookupRelationModel extends DsDynamicInputModel { export class DynamicLookupRelationModel extends DsDynamicInputModel {
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_RELATION; @serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_RELATION;
@serializable() value: any; @serializable() value: any[];
constructor(config: DynamicLookupRelationModelConfig, layout?: DynamicFormControlLayout) { constructor(config: DynamicLookupRelationModelConfig, layout?: DynamicFormControlLayout) {

View File

@@ -12,7 +12,6 @@
(dfFocus)="onFocus($event)"> (dfFocus)="onFocus($event)">
<ng-template modelType="ARRAY" let-group let-index="index" let-context="context"> <ng-template modelType="ARRAY" let-group let-index="index" let-context="context">
<!--Array with repeteable items--> <!--Array with repeteable items-->
<div *ngIf="!context.notRepeatable" <div *ngIf="!context.notRepeatable"
class="col-xs-2 d-flex flex-column justify-content-sm-start align-items-end"> class="col-xs-2 d-flex flex-column justify-content-sm-start align-items-end">

View File

@@ -328,13 +328,19 @@ export class PaginationComponent implements OnDestroy, OnInit {
* Method to emit a general pagination change event * Method to emit a general pagination change event
*/ */
private emitPaginationChange() { private emitPaginationChange() {
this.paginationChange.emit({ this.paginationChange.emit(
pageId: this.id, {
page: this.currentPage, pagination: Object.assign(
pageSize: this.pageSize, new PaginationComponentOptions(),
sortDirection: this.sortDirection, {
sortField: this.sortField id: this.id,
}); currentPage: this.currentPage,
pageSize: this.pageSize,
}),
sort: Object.assign(
new SortOptions(this.sortField, this.sortDirection)
)
})
} }
/** /**