mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
62849: progress july 5
This commit is contained in:
@@ -13,7 +13,6 @@ import { combineLatest as combineLatestObservable } from 'rxjs';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
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 { 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 { 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';
|
||||
|
@@ -44,7 +44,6 @@ export abstract class BaseResponseParsingService {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.cache(object, requestUUID);
|
||||
return object;
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
getRemoteDataPayload, getResponseFromEntry,
|
||||
getSucceededRemoteData
|
||||
} from '../shared/operators';
|
||||
import { DeleteRequest, RestRequest } from './request.models';
|
||||
import { DeleteRequest, PostRequest, RestRequest } from './request.models';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { RestResponse } from '../cache/response.models';
|
||||
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
|
||||
* This is used for easier access of a relationship's type because they exist as observables
|
||||
|
@@ -10,7 +10,6 @@ import { BrowseDefinition } from './browse-definition.model';
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
import { SearchResult } from '../../+search-page/search-result.model';
|
||||
import { Item } from './item.model';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
/**
|
||||
|
@@ -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';
|
||||
|
||||
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
|
||||
console.log(model.type);
|
||||
switch (model.type) {
|
||||
case DYNAMIC_FORM_CONTROL_TYPE_ARRAY:
|
||||
return DsDynamicFormArrayComponent;
|
||||
|
@@ -1,26 +1,88 @@
|
||||
<div class="container">
|
||||
<div class="search-page row">
|
||||
<div class="col-12">
|
||||
<div class="row">
|
||||
<div id="search-body">
|
||||
<div id="search-content" class="col-12" *ngVar="(resultsRD$ | async) as resultsRD">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-title">Choose a {{fieldName}}</h4>
|
||||
<button type="button" class="close" aria-label="Close button" aria-describedby="modal-title"
|
||||
(click)="modal.dismiss('Cross click')">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngVar="(resultsRD$ | async) as resultsRD">
|
||||
<ds-loading *ngIf="!resultsRD || resultsRD.isLoading"></ds-loading>
|
||||
<div *ngIf="resultsRD.hasSucceeded">
|
||||
<div *ngIf="repeatable">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<!-- In theory we don't need separate checkboxes for this,
|
||||
but I wasn't able to get this to work correctly without them.
|
||||
Checkboxes that are in the indeterminate state always switch to checked when clicked
|
||||
This seemed like the cleanest and clearest solution to solve this issue for now.
|
||||
-->
|
||||
<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>
|
||||
<ds-pagination
|
||||
[paginationOptions]="searchConfig.pagination"
|
||||
[collectionSize]="resultsRD?.payload?.totalElements"
|
||||
[sortOptions]="searchConfig.sort"
|
||||
[hideGear]="true"
|
||||
[hidePagerWhenSinglePage]="true"
|
||||
(paginationChange)="onPaginationChange($event)">
|
||||
<ul class="lookup-results list-unstyled">
|
||||
<li *ngFor="let result of resultsRD?.payload?.page; let i = index" class="my-4 d-flex">
|
||||
<input type="checkbox" [value]="result.indexableObject.uuid"/>
|
||||
<ds-wrapper-list-element class="result-list-element" [object]="result" [index]="i"></ds-wrapper-list-element>
|
||||
</li>
|
||||
</ul>
|
||||
(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>
|
||||
</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>
|
@@ -1,3 +1,7 @@
|
||||
.result-list-element {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
justify-content: space-between;
|
||||
}
|
@@ -2,11 +2,16 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { PaginatedList } from '../../../../../../core/data/paginated-list';
|
||||
import { SearchResult } from '../../../../../../+search-page/search-result.model';
|
||||
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 { PaginatedSearchOptions } from '../../../../../../+search-page/paginated-search-options.model';
|
||||
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.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=';
|
||||
|
||||
@@ -20,20 +25,111 @@ const RELATION_TYPE_METADATA_PREFIX = 'relation.isPublicationOf';
|
||||
})
|
||||
export class DsDynamicLookupRelationModalComponent implements OnInit {
|
||||
relationKey: string;
|
||||
fieldName: string;
|
||||
resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>;
|
||||
searchConfig: PaginatedSearchOptions;
|
||||
repeatable: boolean = true;
|
||||
selection: DSpaceObject[] = [];
|
||||
allSelected = false;
|
||||
|
||||
constructor(private searchService: SearchService) {
|
||||
constructor(protected modal: NgbActiveModal, private searchService: SearchService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const pagination = Object.assign(new PaginationComponentOptions(), { pageSize: 5 });
|
||||
this.searchConfig = new PaginatedSearchOptions({
|
||||
pagination: pagination,
|
||||
fixedFilter: RELATION_TYPE_FILTER_PREFIX + this.relationKey.substring(RELATION_TYPE_METADATA_PREFIX.length)
|
||||
this.fieldName = this.relationKey.substring(RELATION_TYPE_METADATA_PREFIX.length);
|
||||
const pagination = Object.assign(new PaginationComponentOptions(), {
|
||||
id: 'submission-relation-list',
|
||||
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);
|
||||
}
|
||||
}
|
@@ -3,15 +3,20 @@
|
||||
<!--Simple lookup, first field -->
|
||||
<div class="col right-addon">
|
||||
<input class="form-control"
|
||||
[attr.autoComplete]="model.autoComplete"
|
||||
[class.is-invalid]="showErrorMessages"
|
||||
[dynamicId]="bindId && model.id"
|
||||
[name]="model.name"
|
||||
[type]="model.inputType"
|
||||
[(ngModel)]="model.value"
|
||||
[value]="modalValuesString"
|
||||
[disabled]="model.disabled"
|
||||
[type]="model.inputType"
|
||||
[placeholder]="model.placeholder | translate"
|
||||
[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 class="col-auto text-center">
|
||||
<button class="btn btn-secondary"
|
||||
|
@@ -6,10 +6,12 @@ import {
|
||||
DynamicFormValidationService
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
import { DynamicRelationGroupModel } from '../relation-group/dynamic-relation-group.model';
|
||||
import { isNotEmpty } from '../../../../../empty.util';
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||
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({
|
||||
selector: 'ds-dynamic-lookup-relation',
|
||||
@@ -20,22 +22,26 @@ export class DsDynamicLookupRelationComponent extends DynamicFormControlComponen
|
||||
|
||||
@Input() formId: string;
|
||||
@Input() group: FormGroup;
|
||||
@Input() model: DynamicRelationGroupModel;
|
||||
@Input() model: DynamicLookupRelationModel;
|
||||
|
||||
@Output() blur: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() change: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
modalRef: NgbModalRef;
|
||||
modalValuesString = '';
|
||||
currentObjects;
|
||||
|
||||
constructor(private modalService: NgbModal,
|
||||
protected layoutService: DynamicFormLayoutService,
|
||||
protected validationService: DynamicFormValidationService
|
||||
protected validationService: DynamicFormValidationService,
|
||||
private relationshipService: RelationshipService
|
||||
) {
|
||||
super(layoutService, validationService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.currentObjects = this.relationshipService.getItemRelationshipLabels();
|
||||
}
|
||||
|
||||
public hasEmptyValue() {
|
||||
@@ -45,8 +51,9 @@ export class DsDynamicLookupRelationComponent extends DynamicFormControlComponen
|
||||
openLookup() {
|
||||
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent);
|
||||
this.modalRef.componentInstance.relationKey = this.model.name;
|
||||
this.modalRef.result.then((result) => {
|
||||
this.model.value = result;
|
||||
this.modalRef.result.then((resultList) => {
|
||||
this.model.value = resultList;
|
||||
this.modalValuesString = resultList.map((dso: DSpaceObject) => dso.name).join('; ');
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,7 @@ export interface DynamicLookupRelationModelConfig extends DsDynamicInputModelCon
|
||||
export class DynamicLookupRelationModel extends DsDynamicInputModel {
|
||||
|
||||
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_RELATION;
|
||||
@serializable() value: any;
|
||||
@serializable() value: any[];
|
||||
|
||||
constructor(config: DynamicLookupRelationModelConfig, layout?: DynamicFormControlLayout) {
|
||||
|
||||
|
@@ -12,7 +12,6 @@
|
||||
(dfFocus)="onFocus($event)">
|
||||
|
||||
<ng-template modelType="ARRAY" let-group let-index="index" let-context="context">
|
||||
|
||||
<!--Array with repeteable items-->
|
||||
<div *ngIf="!context.notRepeatable"
|
||||
class="col-xs-2 d-flex flex-column justify-content-sm-start align-items-end">
|
||||
|
@@ -328,13 +328,19 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
||||
* Method to emit a general pagination change event
|
||||
*/
|
||||
private emitPaginationChange() {
|
||||
this.paginationChange.emit({
|
||||
pageId: this.id,
|
||||
page: this.currentPage,
|
||||
this.paginationChange.emit(
|
||||
{
|
||||
pagination: Object.assign(
|
||||
new PaginationComponentOptions(),
|
||||
{
|
||||
id: this.id,
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
sortDirection: this.sortDirection,
|
||||
sortField: this.sortField
|
||||
});
|
||||
}),
|
||||
sort: Object.assign(
|
||||
new SortOptions(this.sortField, this.sortDirection)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user