diff --git a/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.html b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.html new file mode 100644 index 00000000000..b0ad0f80662 --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.html @@ -0,0 +1,17 @@ +
+
+
+
+ +
+
+
+ + + +
+ +
+
+
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.scss b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.scss new file mode 100644 index 00000000000..4eeee69785d --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.scss @@ -0,0 +1,9 @@ +:host{ + img { + height: 80px; + width: 80px; + border: 1px solid #ccc; + border-radius: 50%; + object-fit: cover; + } +} diff --git a/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.spec.ts b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.spec.ts new file mode 100644 index 00000000000..79c06b00fc9 --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.spec.ts @@ -0,0 +1,47 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MetadataLinkViewAvatarPopoverComponent } from './metadata-link-view-avatar-popover.component'; +import { of as observableOf } from 'rxjs'; +import { AuthService } from '../../../core/auth/auth.service'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { FileService } from '../../../core/shared/file.service'; + +describe('MetadataLinkViewAvatarPopoverComponent', () => { + let component: MetadataLinkViewAvatarPopoverComponent; + let fixture: ComponentFixture; + let authService; + let authorizationService; + let fileService; + + beforeEach(async(() => { + authService = jasmine.createSpyObj('AuthService', { + isAuthenticated: observableOf(true), + }); + authorizationService = jasmine.createSpyObj('AuthorizationService', { + isAuthorized: observableOf(true), + }); + fileService = jasmine.createSpyObj('FileService', { + retrieveFileDownloadLink: null + }); + TestBed.configureTestingModule({ + declarations: [ MetadataLinkViewAvatarPopoverComponent ], + providers: [ + { provide: AuthService, useValue: authService }, + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: FileService, useValue: fileService } + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MetadataLinkViewAvatarPopoverComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.ts b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.ts new file mode 100644 index 00000000000..20c2443c45b --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { ThumbnailComponent } from 'src/app/thumbnail/thumbnail.component'; + +@Component({ + selector: 'ds-metadata-link-view-avatar-popover', + templateUrl: './metadata-link-view-avatar-popover.component.html', + styleUrls: ['./metadata-link-view-avatar-popover.component.scss'] +}) +export class MetadataLinkViewAvatarPopoverComponent extends ThumbnailComponent { + + /** + * The fallback image to use when the thumbnail is not available + */ + fallbackImage = 'assets/images/person-placeholder.svg'; +} diff --git a/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.html b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.html new file mode 100644 index 00000000000..7a3ae54facb --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.html @@ -0,0 +1,15 @@ + + + {{ metadataValue }} + + + {{ metadataValue }} + + orcid-logo + + diff --git a/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.scss b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.scss new file mode 100644 index 00000000000..b92a52cd35d --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.scss @@ -0,0 +1,4 @@ +.orcid-icon { + height: 1.2rem; + padding-left: 0.3rem; +} diff --git a/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.spec.ts b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.spec.ts new file mode 100644 index 00000000000..2e4be5cafaa --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.spec.ts @@ -0,0 +1,69 @@ +import { ConfigurationDataService } from './../../../core/data/configuration-data.service'; +import { Item } from 'src/app/core/shared/item.model'; +import { TranslateLoaderMock } from './../../testing/translate-loader.mock'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MetadataLinkViewOrcidComponent } from './metadata-link-view-orcid.component'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { MetadataValue } from 'src/app/core/shared/metadata.models'; +import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; + +describe('MetadataLinkViewOrcidComponent', () => { + let component: MetadataLinkViewOrcidComponent; + let fixture: ComponentFixture; + + const configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: createSuccessfulRemoteDataObject$({ values: ['https://sandbox.orcid.org'] }) + }); + + + const metadataValue = Object.assign(new MetadataValue(), { + 'value': '0000-0001-8918-3592', + 'language': 'en_US', + 'authority': null, + 'confidence': -1, + 'place': 0 + }); + + const testItem = Object.assign(new Item(), + { + type: 'item', + metadata: { + 'person.identifier.orcid': [metadataValue], + 'dspace.orcid.authenticated': [ + { + language: null, + value: 'authenticated' + } + ] + }, + uuid: 'test-item-uuid', + } + ); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MetadataLinkViewOrcidComponent ], + imports: [TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), BrowserAnimationsModule], + providers: [ + { provide: ConfigurationDataService, useValue: configurationDataService} + ], + }) + .compileComponents(); + + fixture = TestBed.createComponent(MetadataLinkViewOrcidComponent); + component = fixture.componentInstance; + component.itemValue = testItem; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.ts b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.ts new file mode 100644 index 00000000000..a0252732c71 --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component.ts @@ -0,0 +1,46 @@ +import { ConfigurationProperty } from './../../../core/shared/configuration-property.model'; +import { getFirstSucceededRemoteDataPayload } from './../../../core/shared/operators'; +import { ConfigurationDataService } from './../../../core/data/configuration-data.service'; +import { Component, Input, OnInit } from '@angular/core'; +import { Item } from '../../../core/shared/item.model'; +import { Observable, map } from 'rxjs'; + +@Component({ + selector: 'ds-metadata-link-view-orcid', + templateUrl: './metadata-link-view-orcid.component.html', + styleUrls: ['./metadata-link-view-orcid.component.scss'], +}) +export class MetadataLinkViewOrcidComponent implements OnInit { + /** + * Item value to display the metadata for + */ + @Input() itemValue: Item; + + metadataValue: string; + + orcidUrl$: Observable; + + constructor(protected configurationService: ConfigurationDataService) {} + + ngOnInit(): void { + this.orcidUrl$ = this.configurationService + .findByPropertyName('orcid.domain-url') + .pipe( + getFirstSucceededRemoteDataPayload(), + map((property: ConfigurationProperty) => + property?.values?.length > 0 ? property.values[0] : null + ) + ); + this.metadataValue = this.itemValue.firstMetadataValue( + 'person.identifier.orcid' + ); + } + + public hasOrcid(): boolean { + return this.itemValue.hasMetadata('person.identifier.orcid'); + } + + public hasOrcidBadge(): boolean { + return this.itemValue.hasMetadata('dspace.orcid.authenticated'); + } +} diff --git a/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.html b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.html new file mode 100644 index 00000000000..e53886331fe --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.html @@ -0,0 +1,62 @@ +
+ +
+ + {{item.firstMetadataValue('dc.title')}} +
+ + +
+
+ {{ "metadata-link-view.popover.label." + (isOtherEntityType ? "other" : item.entityType) + "." + metadata | translate }} +
+
+ + {{ item.firstMetadataValue(metadata) }} + + + {{ item.firstMetadataValue(metadata) }} + + + + +
+ + + {{ item.firstMetadataValue(metadata) }} + +
+
+
+
+ + +
diff --git a/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.scss b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.scss new file mode 100644 index 00000000000..0ddfc98b6cd --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.scss @@ -0,0 +1,6 @@ +.source-icon { + height: var(--ds-identifier-sybetype-icon-height); + min-height: 16px; + width: auto; + padding-left: 0.3rem; +} diff --git a/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.spec.ts b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.spec.ts new file mode 100644 index 00000000000..182902de7f7 --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.spec.ts @@ -0,0 +1,131 @@ +import { MetadataValueFilter } from 'src/app/core/shared/metadata.models'; +import { Item } from 'src/app/core/shared/item.model'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MetadataLinkViewPopoverComponent } from './metadata-link-view-popover.component'; +import { environment } from 'src/environments/environment.test'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; +import { Bitstream } from 'src/app/core/shared/bitstream.model'; + +describe('MetadataLinkViewPopoverComponent', () => { + let component: MetadataLinkViewPopoverComponent; + let fixture: ComponentFixture; + + + const itemMock = Object.assign(new Item(), { + uuid: '1234-1234-1234-1234', + + firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string { + return itemMock.metadata[keyOrKeys as string][0].value; + }, + + metadata: { + 'dc.title': [ + { + value: 'file name', + language: null + } + ], + 'dc.identifier.uri': [ + { + value: 'http://example.com', + language: null + } + ], + 'dc.description.abstract': [ + { + value: 'Long text description', + language: null + } + ], + 'organization.identifier.ror': [ + { + value: 'https://ror.org/1234', + language: null + } + ], + 'person.identifier.orcid': [ + { + value: 'https://orcid.org/0000-0000-0000-0000', + language: null + } + ], + 'dspace.entity.type': [ + { + value: 'Person', + language: null + } + ] + }, + thumbnail: createSuccessfulRemoteDataObject$(new Bitstream()) + }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MetadataLinkViewPopoverComponent ], + imports: [TranslateModule.forRoot()], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MetadataLinkViewPopoverComponent); + component = fixture.componentInstance; + component.item = itemMock; + itemMock.firstMetadataValue = jasmine.createSpy() + .withArgs('dspace.entity.type').and.returnValue('Person') + .withArgs('dc.title').and.returnValue('Test Title') + .withArgs('dc.identifier.uri').and.returnValue('http://example.com') + .withArgs('dc.description.abstract').and.returnValue('Long text description') + .withArgs('organization.identifier.ror').and.returnValue('https://ror.org/1234') + .withArgs('person.identifier.orcid').and.returnValue('https://orcid.org/0000-0000-0000-0000'); + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should display the item title', () => { + const titleElement = fixture.debugElement.query(By.css('.font-weight-bold.h4')); + expect(titleElement.nativeElement.textContent).toContain('Test Title'); + }); + + it('should display a link for each metadata field that is a valid link', () => { + component.entityMetdataFields = ['dc.identifier.uri']; + fixture.detectChanges(); + const linkElement = fixture.debugElement.query(By.css('a[href="http://example.com"]')); + expect(linkElement).toBeTruthy(); + }); + + it('should retrieve the identifier subtype configuration based on the given metadata value', () => { + const metadataValue = 'organization.identifier.ror'; + const expectedSubtypeConfig = environment.identifierSubtypes.find((config) => config.name === 'ror'); + expect(component.getSourceSubTypeIdentifier(metadataValue)).toEqual(expectedSubtypeConfig); + }); + + + it('should check if a given metadata value is a valid link', () => { + const validLink = 'http://example.com'; + const invalidLink = 'not a link'; + expect(component.isLink(validLink)).toBeTrue(); + expect(component.isLink(invalidLink)).toBeFalse(); + }); + + it('should display the "more info" link with the correct router link', () => { + spyOn(component, 'getItemPageRoute').and.returnValue('/item/' + itemMock.uuid); + fixture.detectChanges(); + const moreInfoLinkElement = fixture.debugElement.query(By.css('a[data-test="more-info-link"]')); + expect(moreInfoLinkElement.nativeElement.routerLink).toContain('/item/' + itemMock.uuid); + }); + + it('should display the avatar popover when item has a thumbnail', () => { + const avatarPopoverElement = fixture.debugElement.query(By.css('ds-metadata-link-view-avatar-popover')); + expect(avatarPopoverElement).toBeTruthy(); + }); +}); diff --git a/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.ts b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.ts new file mode 100644 index 00000000000..9984621835e --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component.ts @@ -0,0 +1,94 @@ +import { IdentifierSubtypesConfig } from './../../../../config/identifier-subtypes-config.interface'; +import { MetadataLinkViewPopoverDataConfig } from 'src/config/metadata-link-view-popoverdata-config.interface'; +import { Item } from './../../../core/shared/item.model'; +import { Component, Input, OnInit } from '@angular/core'; +import { environment } from 'src/environments/environment'; +import { hasNoValue, hasValue } from '../../empty.util'; + +import { AuthorithyIcon } from 'src/config/submission-config.interface'; +import { getItemPageRoute } from 'src/app/item-page/item-page-routing-paths'; + +@Component({ + selector: 'ds-metadata-link-view-popover', + templateUrl: './metadata-link-view-popover.component.html', + styleUrls: ['./metadata-link-view-popover.component.scss'] +}) +export class MetadataLinkViewPopoverComponent implements OnInit { + + /** + * The item to display the metadata for + */ + @Input() item: Item; + + /** + * The metadata link view popover data configuration. + * This configuration is used to determine which metadata fields to display for the given entity type + */ + metadataLinkViewPopoverData: MetadataLinkViewPopoverDataConfig = environment.metadataLinkViewPopoverData; + + /** + * The metadata fields to display for the given entity type + */ + entityMetdataFields: string[] = []; + + /** + * The metadata fields including long text metadata values. + * These metadata values should be truncated to a certain length. + */ + longTextMetadataList = ['dc.description.abstract', 'dc.description']; + + /** + * The source icons configuration + */ + sourceIcons: AuthorithyIcon[] = environment.submission.icons.authority.sourceIcons; + + /** + * The identifier subtype configurations + */ + identifierSubtypeConfig: IdentifierSubtypesConfig[] = environment.identifierSubtypes; + + /** + * Whether the entity type is not found in the metadataLinkViewPopoverData configuration + */ + isOtherEntityType = false; + + /** + * If `metadataLinkViewPopoverData` is provided, it retrieves the metadata fields based on the entity type. + * If no metadata fields are found for the entity type, it falls back to the fallback metadata list. + */ + ngOnInit() { + if (this.metadataLinkViewPopoverData) { + const metadataFields = this.metadataLinkViewPopoverData.entityDataConfig.find((config) => config.entityType === this.item.entityType); + this.entityMetdataFields = hasValue(metadataFields) ? metadataFields.metadataList : this.metadataLinkViewPopoverData.fallbackMetdataList; + this.isOtherEntityType = hasNoValue(metadataFields); + } + } + + /** + * Checks if the given metadata value is a valid link. + */ + isLink(metadataValue: string): boolean { + const urlRegex = /^(http|https):\/\/[^ "]+$/; + return urlRegex.test(metadataValue); + } + + /** + * Returns the page route for the item. + * @returns The page route for the item. + */ + getItemPageRoute(): string { + return getItemPageRoute(this.item); + } + + /** + * Retrieves the identifier subtype configuration based on the given metadata value. + * @param metadataValue - The metadata value used to determine the identifier subtype. + * @returns The identifier subtype configuration object. + */ + getSourceSubTypeIdentifier(metadataValue: string): IdentifierSubtypesConfig { + const metadataValueSplited = metadataValue.split('.'); + const subtype = metadataValueSplited[metadataValueSplited.length - 1]; + const identifierSubtype = this.identifierSubtypeConfig.find((config) => config.name === subtype); + return identifierSubtype; + } +} diff --git a/src/app/shared/metadata-link-view/metadata-link-view.component.html b/src/app/shared/metadata-link-view/metadata-link-view.component.html index b9dc21d1b32..d7fb1150e7b 100644 --- a/src/app/shared/metadata-link-view/metadata-link-view.component.html +++ b/src/app/shared/metadata-link-view/metadata-link-view.component.html @@ -2,15 +2,24 @@ - - - {{metadataView.value}} - + + + + {{metadataView.value}} + + + {{normalizeValue(metadataView.value)}} + + + + + diff --git a/src/app/shared/metadata-link-view/metadata-link-view.component.scss b/src/app/shared/metadata-link-view/metadata-link-view.component.scss index b92a52cd35d..e9051149d7f 100644 --- a/src/app/shared/metadata-link-view/metadata-link-view.component.scss +++ b/src/app/shared/metadata-link-view/metadata-link-view.component.scss @@ -2,3 +2,9 @@ height: 1.2rem; padding-left: 0.3rem; } + + +::ng-deep .popover { + max-width: 400px !important; + min-width: 300px !important; +} diff --git a/src/app/shared/metadata-link-view/metadata-link-view.component.ts b/src/app/shared/metadata-link-view/metadata-link-view.component.ts index 800897f3387..c38e1cc2dac 100644 --- a/src/app/shared/metadata-link-view/metadata-link-view.component.ts +++ b/src/app/shared/metadata-link-view/metadata-link-view.component.ts @@ -13,14 +13,8 @@ import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { Metadata } from '../../core/shared/metadata.utils'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { environment } from '../../../environments/environment'; - -interface MetadataView { - authority: string; - value: string; - orcidAuthenticated: string; - entityType: string; - entityStyle: string|string[]; -} +import { followLink } from '../utils/follow-link-config.model'; +import { MetadataView } from './metadata-view.model'; @Component({ selector: 'ds-metadata-link-view', @@ -58,6 +52,11 @@ export class MetadataLinkViewComponent implements OnInit { */ iconPosition = 'after'; + /** + * Related item of the metadata value + */ + relatedItem: Item; + /** * Map all entities with the icons specified in the environment configuration file */ @@ -68,45 +67,67 @@ export class MetadataLinkViewComponent implements OnInit { */ ngOnInit(): void { this.metadataView$ = observableOf(this.metadata).pipe( - switchMap((metadataValue: MetadataValue) => { - if (Metadata.hasValidAuthority(metadataValue.authority)) { - return this.itemService.findById(metadataValue.authority).pipe( - getFirstCompletedRemoteData(), - map((itemRD: RemoteData) => { - if (itemRD.hasSucceeded) { - const entityStyleValue = this.getCrisRefMetadata(itemRD.payload?.entityType); - return { - authority: metadataValue.authority, - value: metadataValue.value, - orcidAuthenticated: this.getOrcid(itemRD.payload), - entityType: itemRD.payload?.entityType, - entityStyle: itemRD.payload?.firstMetadataValue(entityStyleValue) - }; - } else { - return { - authority: null, - value: metadataValue.value, - orcidAuthenticated: null, - entityType: 'PRIVATE', - entityStyle: this.metadataName - }; - } - }) - ); - } else { - return observableOf({ - authority: null, - value: metadataValue.value, - orcidAuthenticated: null, - entityType: null, - entityStyle: null - }); - } - }), + switchMap((metadataValue: MetadataValue) => this.getMetadataView(metadataValue)), take(1) ); } + + /** + * Retrieves the metadata view for a given metadata value. + * If the metadata value has a valid authority, it retrieves the item using the authority and creates a metadata view. + * If the metadata value does not have a valid authority, it creates a metadata view with null values. + * + * @param metadataValue The metadata value for which to retrieve the metadata view. + * @returns An Observable that emits the metadata view. + */ + private getMetadataView(metadataValue: MetadataValue): Observable { + const linksToFollow = [followLink('thumbnail')]; + + if (Metadata.hasValidAuthority(metadataValue.authority)) { + return this.itemService.findById(metadataValue.authority, true, false, ...linksToFollow).pipe( + getFirstCompletedRemoteData(), + map((itemRD: RemoteData) => this.createMetadataView(itemRD, metadataValue)) + ); + } else { + return observableOf({ + authority: null, + value: metadataValue.value, + orcidAuthenticated: null, + entityType: null, + entityStyle: null + }); + } + } + + /** + * Creates a MetadataView object based on the provided itemRD and metadataValue. + * @param itemRD - The RemoteData object containing the item information. + * @param metadataValue - The MetadataValue object containing the metadata information. + * @returns The created MetadataView object. + */ + private createMetadataView(itemRD: RemoteData, metadataValue: MetadataValue): MetadataView { + if (itemRD.hasSucceeded) { + this.relatedItem = itemRD.payload; + const entityStyleValue = this.getCrisRefMetadata(itemRD.payload?.entityType); + return { + authority: metadataValue.authority, + value: metadataValue.value, + orcidAuthenticated: this.getOrcid(itemRD.payload), + entityType: itemRD.payload?.entityType, + entityStyle: itemRD.payload?.firstMetadataValue(entityStyleValue) + }; + } else { + return { + authority: null, + value: metadataValue.value, + orcidAuthenticated: null, + entityType: 'PRIVATE', + entityStyle: this.metadataName + }; + } + } + /** * Returns the orcid for given item, or null if there is no metadata authenticated for person * diff --git a/src/app/shared/metadata-link-view/metadata-view.model.ts b/src/app/shared/metadata-link-view/metadata-view.model.ts new file mode 100644 index 00000000000..569418d0b24 --- /dev/null +++ b/src/app/shared/metadata-link-view/metadata-view.model.ts @@ -0,0 +1,7 @@ +export interface MetadataView { + authority: string; + value: string; + orcidAuthenticated: string; + entityType: string; + entityStyle: string|string[]; +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index e4e88c3c9ea..63337db4359 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -352,6 +352,9 @@ import { ItemCollectionComponent } from './object-collection/shared/mydspace-ite import { ThemedItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; import { ItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component'; import { MarkdownDirective } from './utils/markdown.directive'; +import { MetadataLinkViewPopoverComponent } from './metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component'; +import { MetadataLinkViewAvatarPopoverComponent } from './metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component'; +import { MetadataLinkViewOrcidComponent } from './metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component'; const MODULES = [ CommonModule, @@ -502,6 +505,9 @@ const COMPONENTS = [ BrowseMostElementsComponent, EditMetadataSecurityComponent, MetadataLinkViewComponent, + MetadataLinkViewPopoverComponent, + MetadataLinkViewAvatarPopoverComponent, + MetadataLinkViewOrcidComponent, ExportExcelSelectorComponent, ThemedBrowseMostElementsComponent, SearchChartBarHorizontalComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 9532bf14ec0..94ca8ed18bc 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7423,4 +7423,52 @@ "meta.tag.missing.description": "No description available", + "metadata-link-view.popover.label.Person.dc.title": "Fullname", + + "metadata-link-view.popover.label.Person.person.affiliation.name": "Main affiliation", + + "metadata-link-view.popover.label.Person.person.email": "Email", + + "metadata-link-view.popover.label.Person.person.identifier.orcid": "ORCID", + + "metadata-link-view.popover.label.Person.dc.description.abstract": "Abstract", + + "metadata-link-view.popover.label.OrgUnit.dc.title": "Fullname", + + "metadata-link-view.popover.label.OrgUnit.organization.identifier.ror": "ROR", + + "metadata-link-view.popover.label.OrgUnit.crisou.director": "Director", + + "metadata-link-view.popover.label.OrgUnit.organization.parentOrganization": "Parent Organization", + + "metadata-link-view.popover.label.OrgUnit.dc.description.abstract": "Description", + + "metadata-link-view.popover.label.Project.dc.title": "Title", + + "metadata-link-view.popover.label.Project.oairecerif.project.status": "Status", + + "metadata-link-view.popover.label.Project.dc.description.abstract": "Abstract", + + "metadata-link-view.popover.label.Funding.dc.title": "Title", + + "metadata-link-view.popover.label.Funding.oairecerif.funder": "Funder", + + "metadata-link-view.popover.label.Funding.oairecerif.fundingProgram": "Funding program", + + "metadata-link-view.popover.label.Funding.dc.description.abstract": "Abstract", + + "metadata-link-view.popover.label.Publication.dc.title": "Title", + + "metadata-link-view.popover.label.Publication.dc.identifier.doi": "DOI", + + "metadata-link-view.popover.label.Publication.dc.identifier.uri": "URL", + + "metadata-link-view.popover.label.Publication.dc.description.abstract": "Abstract", + + "metadata-link-view.popover.label.other.dc.title": "Title", + + "metadata-link-view.popover.label.other.dc.description.abstract": "Description", + + "metadata-link-view.popover.label.more-info": "More info", + } diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index 5b21141695d..589999d4746 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -34,6 +34,7 @@ import { SearchResultConfig } from './search-result-config.interface'; import { MiradorConfig } from './mirador-config.interfaces'; import { LoaderConfig } from './loader-config.interfaces'; import { MetaTagsConfig } from './meta-tags.config'; +import { MetadataLinkViewPopoverDataConfig } from './metadata-link-view-popoverdata-config.interface'; import { IdentifierSubtypesConfig } from './identifier-subtypes-config.interface'; import { DatadogRumConfig } from './datadog-rum-config.interfaces'; @@ -76,6 +77,7 @@ interface AppConfig extends Config { mirador: MiradorConfig; loader: LoaderConfig; metaTags: MetaTagsConfig; + metadataLinkViewPopoverData: MetadataLinkViewPopoverDataConfig; identifierSubtypes: IdentifierSubtypesConfig[]; datadogRum?: DatadogRumConfig; } diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 8ad8c884b71..e9c23e43cab 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -37,6 +37,7 @@ import { SearchResultConfig } from './search-result-config.interface'; import { MiradorConfig } from './mirador-config.interfaces'; import { LoaderConfig } from './loader-config.interfaces'; import { MetaTagsConfig } from './meta-tags.config'; +import { MetadataLinkViewPopoverDataConfig } from './metadata-link-view-popoverdata-config.interface'; import { IdentifierSubtypesConfig, IdentifierSubtypesIconPositionEnum } from './identifier-subtypes-config.interface'; import { DatadogRumConfig } from './datadog-rum-config.interfaces'; @@ -807,6 +808,35 @@ export class DefaultAppConfig implements AppConfig { 'DSpace-CRIS enables secure, integrated and interoperable research information and data management – in a single solution.' }; + // Configuration for the metadata link view popover + metadataLinkViewPopoverData: MetadataLinkViewPopoverDataConfig = + { + fallbackMetdataList: ['dc.description.abstract'], + + entityDataConfig: [ + { + entityType: 'Person', + metadataList: ['person.affiliation.name', 'person.email', 'person.identifier.orcid', 'dc.description.abstract'] + }, + { + entityType: 'OrgUnit', + metadataList: ['organization.parentOrganization', 'organization.identifier.ror', 'crisou.director', 'dc.description.abstract'] + }, + { + entityType: 'Project', + metadataList: ['oairecerif.project.status', 'dc.description.abstract'] + }, + { + entityType: 'Funding', + metadataList: ['oairecerif.funder', 'oairecerif.fundingProgram', 'dc.description.abstract'] + }, + { + entityType: 'Publication', + metadataList: ['dc.identifier.doi', 'dc.identifier.uri', 'dc.description.abstract'] + }, + ] + }; + identifierSubtypes: IdentifierSubtypesConfig[] = [ { name: 'ror', diff --git a/src/config/metadata-link-view-popoverdata-config.interface.ts b/src/config/metadata-link-view-popoverdata-config.interface.ts new file mode 100644 index 00000000000..01ee9a80723 --- /dev/null +++ b/src/config/metadata-link-view-popoverdata-config.interface.ts @@ -0,0 +1,23 @@ +export interface MetadataLinkViewPopoverDataConfig { + /** + * The list of entity types to display the metadata for + */ + entityDataConfig: EntityDataConfig[]; + + /** + * The list of metadata keys to fallback to + */ + fallbackMetdataList: string[]; +} + + +export interface EntityDataConfig { + /** + * The metadata entity type + */ + entityType: string; + /** + * The list of metadata keys to display + */ + metadataList: string[]; +} diff --git a/src/config/submission-config.interface.ts b/src/config/submission-config.interface.ts index c180bfae014..935323ec777 100644 --- a/src/config/submission-config.interface.ts +++ b/src/config/submission-config.interface.ts @@ -10,7 +10,7 @@ interface TypeBindConfig extends Config { field: string; } -interface AuthorithyIcon { +export interface AuthorithyIcon { source: string, path: string } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 364590dab37..ce98bc1ae65 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -598,5 +598,33 @@ export const environment: BuildConfig = { iconPosition: IdentifierSubtypesIconPositionEnum.LEFT, link: 'https://ror.org' } - ] + ], + // Configuration for the metadata link view popover + metadataLinkViewPopoverData: + { + fallbackMetdataList: ['dc.description.abstract'], + + entityDataConfig: [ + { + entityType: 'Person', + metadataList: ['person.affiliation.name', 'person.email', 'person.identifier.orcid', 'dc.description.abstract'] + }, + { + entityType: 'OrgUnit', + metadataList: ['organization.parentOrganization', 'organization.identifier.ror', 'crisou.director', 'dc.description.abstract'] + }, + { + entityType: 'Project', + metadataList: ['oairecerif.project.status', 'dc.description.abstract'] + }, + { + entityType: 'Funding', + metadataList: ['oairecerif.funder', 'oairecerif.fundingProgram', 'dc.description.abstract'] + }, + { + entityType: 'Publication', + metadataList: ['dc.identifier.doi', 'dc.identifier.uri', 'dc.description.abstract'] + }, + ] + }, };