74199: Admin search dialogs - sidebar search list element implementations - intermediate commit

This commit is contained in:
Kristof De Langhe
2020-10-21 13:24:21 +02:00
parent ac44bb9cb9
commit f79ea2b844
13 changed files with 349 additions and 18 deletions

View File

@@ -0,0 +1,38 @@
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { Context } from '../../../../../core/shared/context.model';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { Component } from '@angular/core';
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
import { Item } from '../../../../../core/shared/item.model';
import { isNotEmpty } from '../../../../../shared/empty.util';
@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@Component({
selector: 'ds-journal-issue-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
})
/**
* Component displaying a list element for a {@link ItemSearchResult} of type "JournalIssue" within the context of
* a sidebar search modal
*/
export class JournalIssueSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
/**
* Get the description of the Journal Issue by returning its volume number(s) and/or issue number(s)
*/
getDescription(): string {
const volumeNumbers = this.allMetadataValues(['publicationvolume.volumeNumber']);
const issueNumbers = this.allMetadataValues(['publicationissue.issueNumber']);
let description = '';
if (isNotEmpty(volumeNumbers)) {
description += volumeNumbers.join(', ');
}
if (isNotEmpty(description) && isNotEmpty(issueNumbers)) {
description += ' - ';
}
if (isNotEmpty(issueNumbers)) {
description += issueNumbers.join(', ');
}
return this.undefinedIfEmpty(description);
}
}

View File

@@ -0,0 +1,38 @@
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { Context } from '../../../../../core/shared/context.model';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { Component } from '@angular/core';
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
import { Item } from '../../../../../core/shared/item.model';
import { isNotEmpty } from '../../../../../shared/empty.util';
@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@Component({
selector: 'ds-journal-volume-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
})
/**
* Component displaying a list element for a {@link ItemSearchResult} of type "JournalVolume" within the context of
* a sidebar search modal
*/
export class JournalVolumeSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
/**
* Get the description of the Journal Volume by returning the journal title and volume number(s) (between parentheses)
*/
getDescription(): string {
const titles = this.allMetadataValues(['journal.title']);
const numbers = this.allMetadataValues(['publicationvolume.volumeNumber']);
let description = '';
if (isNotEmpty(titles)) {
description += titles.join(', ');
}
if (isNotEmpty(numbers)) {
if (isNotEmpty(description)) {
description += ' ';
}
description += numbers.map((n) => `(${n})`).join(' ');
}
return this.undefinedIfEmpty(description);
}
}

View File

@@ -0,0 +1,31 @@
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { Context } from '../../../../../core/shared/context.model';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { Component } from '@angular/core';
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
import { Item } from '../../../../../core/shared/item.model';
import { isNotEmpty } from '../../../../../shared/empty.util';
@listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@Component({
selector: 'ds-journal-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
})
/**
* Component displaying a list element for a {@link ItemSearchResult} of type "Journal" within the context of
* a sidebar search modal
*/
export class JournalSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
/**
* Get the description of the Journal by returning its ISSN(s)
*/
getDescription(): string {
const issns = this.allMetadataValues(['creativeworkseries.issn']);
let description = '';
if (isNotEmpty(issns)) {
description += issns.join(', ');
}
return this.undefinedIfEmpty(description);
}
}

View File

@@ -18,6 +18,9 @@ import { JournalIssueSearchResultListElementComponent } from './item-list-elemen
import { JournalVolumeSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component';
import { JournalIssueSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component';
import { JournalVolumeSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component';
import { JournalVolumeSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component';
import { JournalIssueSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component';
import { JournalSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component';
const ENTRY_COMPONENTS = [
JournalComponent,
@@ -34,7 +37,10 @@ const ENTRY_COMPONENTS = [
JournalVolumeSearchResultListElementComponent,
JournalIssueSearchResultGridElementComponent,
JournalVolumeSearchResultGridElementComponent,
JournalSearchResultGridElementComponent
JournalSearchResultGridElementComponent,
JournalVolumeSidebarSearchListElementComponent,
JournalIssueSidebarSearchListElementComponent,
JournalSidebarSearchListElementComponent,
];
@NgModule({

View File

@@ -0,0 +1,33 @@
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { Context } from '../../../../../core/shared/context.model';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { Component } from '@angular/core';
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
import { Item } from '../../../../../core/shared/item.model';
import { isNotEmpty } from '../../../../../shared/empty.util';
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@Component({
selector: 'ds-org-unit-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
})
/**
* Component displaying a list element for a {@link ItemSearchResult} of type "OrgUnit" within the context of
* a sidebar search modal
*/
export class OrgUnitSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
/**
* Get the title of the Org Unit by returning its legal name
*/
getTitle(): string {
return this.firstMetadataValue('organization.legalName');
}
/**
* Get the description of the Org Unit by returning its dc.description
*/
getDescription(): string {
return this.firstMetadataValue('dc.description');
}
}

View File

@@ -0,0 +1,59 @@
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { Context } from '../../../../../core/shared/context.model';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { Component } from '@angular/core';
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
import { Item } from '../../../../../core/shared/item.model';
import { isNotEmpty } from '../../../../../shared/empty.util';
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
import { LinkService } from '../../../../../core/cache/builders/link.service';
import { TranslateService } from '@ngx-translate/core';
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@Component({
selector: 'ds-person-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
})
/**
* Component displaying a list element for a {@link ItemSearchResult} of type "Person" within the context of
* a sidebar search modal
*/
export class PersonSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
constructor(protected truncatableService: TruncatableService,
protected linkService: LinkService,
protected translateService: TranslateService) {
super(truncatableService, linkService);
}
/**
* Get the title of the Person by returning a combination of its family name and given name (or "No name found")
*/
getTitle(): string {
const familyName = this.firstMetadataValue('person.familyName');
const givenName = this.firstMetadataValue('person.givenName');
let title = '';
if (isNotEmpty(familyName)) {
title = familyName;
}
if (isNotEmpty(title)) {
title += ', ';
}
if (isNotEmpty(givenName)) {
title += givenName;
}
return this.defaultIfEmpty(title, this.translateService.instant('person.listelement.no-title'));
}
/**
* Get the description of the Person by returning its job title(s)
*/
getDescription(): string {
const titles = this.allMetadataValues(['person.jobTitle']);
let description = '';
if (isNotEmpty(titles)) {
description += titles.join(', ');
}
return this.undefinedIfEmpty(description);
}
}

View File

@@ -0,0 +1,26 @@
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { Context } from '../../../../../core/shared/context.model';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { Component } from '@angular/core';
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
import { Item } from '../../../../../core/shared/item.model';
import { isNotEmpty } from '../../../../../shared/empty.util';
@listableObjectComponent('ProjectSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@Component({
selector: 'ds-project-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
})
/**
* Component displaying a list element for a {@link ItemSearchResult} of type "Project" within the context of
* a sidebar search modal
*/
export class ProjectSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
/**
* Projects currently don't support a description
*/
getDescription(): string {
return undefined;
}
}

View File

@@ -26,6 +26,9 @@ import { NameVariantModalComponent } from './submission/name-variant-modal/name-
import { OrgUnitInputSuggestionsComponent } from './submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component';
import { OrgUnitSearchResultListSubmissionElementComponent } from './submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component';
import { ExternalSourceEntryListSubmissionElementComponent } from './submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component';
import { OrgUnitSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/org-unit/org-unit-sidebar-search-list-element.component';
import { PersonSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component';
import { ProjectSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component';
const ENTRY_COMPONENTS = [
OrgUnitComponent,
@@ -50,7 +53,10 @@ const ENTRY_COMPONENTS = [
NameVariantModalComponent,
OrgUnitSearchResultListSubmissionElementComponent,
OrgUnitInputSuggestionsComponent,
ExternalSourceEntryListSubmissionElementComponent
ExternalSourceEntryListSubmissionElementComponent,
OrgUnitSidebarSearchListElementComponent,
PersonSidebarSearchListElementComponent,
ProjectSidebarSearchListElementComponent,
];
@NgModule({

View File

@@ -1 +0,0 @@
<span>Test display for sidebar-search list elements</span>

View File

@@ -12,6 +12,10 @@ import { SidebarSearchListElementComponent } from '../../sidebar-search-list-ele
selector: 'ds-publication-sidebar-search-list-element',
templateUrl: '../../sidebar-search-list-element.component.html'
})
/**
* Component displaying a list element for a {@link ItemSearchResult} of type "Publication" within the context of
* a sidebar search modal
*/
export class PublicationSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
}

View File

@@ -1,3 +1,3 @@
<ds-truncatable-part *ngIf="parentTitle$ | async" [maxLines]="1"><div class="text-secondary">{{ parentTitle$ | async }}</div></ds-truncatable-part>
<ds-truncatable-part [maxLines]="1"><div class="text-primary">{{ title }}</div></ds-truncatable-part>
<ds-truncatable-part [maxLines]="1"><div class="text-secondary">{{ description }}</div></ds-truncatable-part>
<ds-truncatable-part *ngIf="parentTitle$ && parentTitle$ | async" [maxLines]="1"><div class="text-secondary">{{ parentTitle$ | async }}</div></ds-truncatable-part>
<ds-truncatable-part *ngIf="title" [maxLines]="1"><div class="text-primary">{{ title }}</div></ds-truncatable-part>
<ds-truncatable-part *ngIf="description" [maxLines]="1"><div class="text-secondary">{{ description }}</div></ds-truncatable-part>

View File

@@ -2,7 +2,7 @@ import { SearchResult } from '../../search/search-result.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { SearchResultListElementComponent } from '../search-result-list-element/search-result-list-element.component';
import { Component } from '@angular/core';
import { hasValue } from '../../empty.util';
import { hasValue, isNotEmpty } from '../../empty.util';
import { Observable } from 'rxjs/internal/Observable';
import { TruncatableService } from '../../truncatable/truncatable.service';
import { LinkService } from '../../../core/cache/builders/link.service';
@@ -10,14 +10,31 @@ import { find, map } from 'rxjs/operators';
import { ChildHALResource } from '../../../core/shared/child-hal-resource.model';
import { followLink } from '../../utils/follow-link-config.model';
import { RemoteData } from '../../../core/data/remote-data';
import { of as observableOf } from 'rxjs';
@Component({
selector: 'ds-sidebar-search-list-element',
templateUrl: './sidebar-search-list-element.component.html'
})
/**
* Component displaying a list element for a {@link SearchResult} in the sidebar search modal
* It displays the name of the parent, title and description of the object. All of which are customizable in the child
* component by overriding the relevant methods of this component
*/
export class SidebarSearchListElementComponent<T extends SearchResult<K>, K extends DSpaceObject> extends SearchResultListElementComponent<T, K> {
/**
* Observable for the title of the parent object (displayed above the object's title)
*/
parentTitle$: Observable<string>;
/**
* The title for the object to display
*/
title: string;
/**
* A description to display below the title
*/
description: string;
public constructor(protected truncatableService: TruncatableService,
@@ -25,6 +42,9 @@ export class SidebarSearchListElementComponent<T extends SearchResult<K>, K exte
super(truncatableService);
}
/**
* Initialise the component variables
*/
ngOnInit(): void {
super.ngOnInit();
if (hasValue(this.dso)) {
@@ -34,23 +54,92 @@ export class SidebarSearchListElementComponent<T extends SearchResult<K>, K exte
}
}
/**
* Get the title of the object's parent
* Retrieve the parent by using the object's parent link and retrieving its 'dc.title' metadata
*/
getParentTitle(): Observable<string> {
return this.getParent().pipe(
map((parentRD: RemoteData<DSpaceObject>) => {
return parentRD ? parentRD.payload.firstMetadataValue('dc.title') : undefined;
})
);
}
/**
* Get the parent of the object
*/
getParent(): Observable<RemoteData<DSpaceObject>> {
if (typeof (this.dso as any).getParentLinkKey === 'function') {
const propertyName = (this.dso as any).getParentLinkKey();
return this.linkService.resolveLink(this.dso, followLink(propertyName))[propertyName].pipe(
find((parentRD: RemoteData<ChildHALResource & DSpaceObject>) => parentRD.hasSucceeded || parentRD.statusCode === 204)
);
}
return observableOf(undefined);
}
/**
* Get the title of the object
* Default: "dc.title"
*/
getTitle(): string {
return this.firstMetadataValue('dc.title');
}
/**
* Get the description of the object
* Default: "(dc.publisher, dc.date.issued) authors"
*/
getDescription(): string {
// TODO: Expand description
return this.firstMetadataValue('dc.publisher');
const publisher = this.firstMetadataValue('dc.publisher');
const date = this.firstMetadataValue('dc.date.issued');
const authors = this.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);
let description = '';
if (isNotEmpty(publisher) || isNotEmpty(date)) {
description += '(';
}
if (isNotEmpty(publisher)) {
description += publisher;
}
if (isNotEmpty(date)) {
if (isNotEmpty(publisher)) {
description += ', ';
}
description += date;
}
if (isNotEmpty(description)) {
description += ') ';
}
if (isNotEmpty(authors)) {
authors.forEach((author, i) => {
description += author;
if (i < (authors.length - 1)) {
description += '; ';
}
});
}
return this.undefinedIfEmpty(description);
}
getParentTitle(): Observable<string> {
// TODO: Remove cast to "any" and replace with proper type-check
const propertyName = (this.dso as any).getParentLinkKey();
return this.linkService.resolveLink(this.dso, followLink(propertyName))[propertyName].pipe(
find((parentRD: RemoteData<ChildHALResource & DSpaceObject>) => parentRD.hasSucceeded || parentRD.statusCode === 204),
map((parentRD: RemoteData<ChildHALResource & DSpaceObject>) => {
return parentRD.payload.firstMetadataValue('dc.title');
})
);
/**
* Return undefined if the provided string is empty
* @param value Value to check
*/
undefinedIfEmpty(value: string) {
return this.defaultIfEmpty(value, undefined);
}
/**
* Return a default value if the provided string is empty
* @param value Value to check
* @param def Default in case value is empty
*/
defaultIfEmpty(value: string, def: string) {
if (isNotEmpty(value)) {
return value;
} else {
return def;
}
}
}

View File

@@ -2194,6 +2194,8 @@
"person.listelement.badge": "Person",
"person.listelement.no-title": "No name found",
"person.page.birthdate": "Birth Date",
"person.page.email": "Email Address",