Skip to content

Commit

Permalink
Merge pull request DSpace#2719 from 4Science/main_CST-12825
Browse files Browse the repository at this point in the history
ROR Integration - Identifier Visualization
  • Loading branch information
tdonohue authored Feb 14, 2024
2 parents c75e6e2 + 678d089 commit e1c639e
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@
</ds-generic-item-page-field>
</div>
<div class="col-xs-12 col-md-7">
<ds-item-page-img-field
[fields]="['organization.identifier.ror']"
[img]="{
URI: './assets/images/ror-icon.svg',
alt: 'item.page.image.alt.ROR',
heightVar: '--ds-item-page-img-field-ror-inline-height'
}"
[item]="object"
[label]="'orgunit.page.ror'"
[urlRegex]="'(.*)ror.org'"
>
</ds-item-page-img-field>
<ds-related-items
[parentItem]="object"
[relationType]="'isPublicationOfOrgUnit'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<!--
Choose a template. Priority: markdown, link, browse link.
-->
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : (hasLink(mdValue) ? link : (hasBrowseDefinition() ? browselink : simple)));
context: {value: mdValue.value}">
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : (hasLink(mdValue) ? (img != null ? linkImg : link) : (hasBrowseDefinition() ? browselink : simple)));
context: {value: mdValue.value, img}">
</ng-container>
<span class="separator" *ngIf="!last" [innerHTML]="separator"></span>
</ng-container>
Expand All @@ -23,6 +23,17 @@
</a>
</ng-template>

<!-- Render value as a link with icon -->
<ng-template #linkImg let-img="img" let-value="value">
<a [href]="value" class="link-anchor dont-break-out ds-simple-metadata-link" target="_blank">
<img class="link-logo"
[alt]="img.alt | translate"
[style.height]="'var(' + img.heightVar + ', --ds-item-page-img-field-default-inline-height)'"
[src]="img.URI"/>
{{value}}
</a>
</ng-template>

<!-- Render simple value in a span -->
<ng-template #simple let-value="value">
<span class="dont-break-out preserve-line-breaks">{{value}}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
import { BrowseDefinition } from '../../../core/shared/browse-definition.model';
import { hasValue } from '../../../shared/empty.util';
import { VALUE_LIST_BROWSE_DEFINITION } from '../../../core/shared/value-list-browse-definition.resource-type';
import { ImageField } from '../../simple/field-components/specific-field/item-page-field.component';

/**
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
Expand Down Expand Up @@ -55,6 +56,11 @@ export class MetadataValuesComponent implements OnChanges {

@Input() browseDefinition?: BrowseDefinition;

/**
* Optional {@code ImageField} reference that represents an image to be displayed inline.
*/
@Input() img?: ImageField;

ngOnChanges(changes: SimpleChanges): void {
this.renderMarkdown = !!this.appConfig.markdown.enabled && this.enableMarkdown;
}
Expand Down
35 changes: 26 additions & 9 deletions src/app/item-page/item-shared.module.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
import {
RelatedEntitiesSearchComponent
} from './simple/related-entities/related-entities-search/related-entities-search.component';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CommonModule, NgOptimizedImage } from '@angular/common';
import { SearchModule } from '../shared/search/search.module';
import { SharedModule } from '../shared/shared.module';
import { TranslateModule } from '@ngx-translate/core';
import { DYNAMIC_FORM_CONTROL_MAP_FN } from '@ng-dynamic-forms/core';
import { dsDynamicFormControlMapFn } from '../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
import { ItemVersionsDeleteModalComponent } from './versions/item-versions-delete-modal/item-versions-delete-modal.component';
import { ItemVersionsSummaryModalComponent } from './versions/item-versions-summary-modal/item-versions-summary-modal.component';
import {
dsDynamicFormControlMapFn
} from '../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
import {
TabbedRelatedEntitiesSearchComponent
} from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
import {
ItemVersionsDeleteModalComponent
} from './versions/item-versions-delete-modal/item-versions-delete-modal.component';
import {
ItemVersionsSummaryModalComponent
} from './versions/item-versions-summary-modal/item-versions-summary-modal.component';
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
import { GenericItemPageFieldComponent } from './simple/field-components/specific-field/generic/generic-item-page-field.component';
import { MetadataRepresentationListComponent } from './simple/metadata-representation-list/metadata-representation-list.component';
import {
GenericItemPageFieldComponent
} from './simple/field-components/specific-field/generic/generic-item-page-field.component';
import {
MetadataRepresentationListComponent
} from './simple/metadata-representation-list/metadata-representation-list.component';
import { RelatedItemsComponent } from './simple/related-items/related-items-component';
import {
ThemedMetadataRepresentationListComponent
} from './simple/metadata-representation-list/themed-metadata-representation-list.component';
import { ItemPageImgFieldComponent } from './simple/field-components/specific-field/img/item-page-img-field.component';

const ENTRY_COMPONENTS = [
ItemVersionsDeleteModalComponent,
Expand All @@ -32,6 +47,7 @@ const COMPONENTS = [
MetadataRepresentationListComponent,
ThemedMetadataRepresentationListComponent,
RelatedItemsComponent,
ItemPageImgFieldComponent,
];

@NgModule({
Expand All @@ -42,7 +58,8 @@ const COMPONENTS = [
CommonModule,
SearchModule,
SharedModule,
TranslateModule
TranslateModule,
NgOptimizedImage
],
exports: [
...COMPONENTS
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ItemPageImgFieldComponent } from './item-page-img-field.component';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment';
import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service';
import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub';
import { GenericItemPageFieldComponent } from '../generic/generic-item-page-field.component';
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component.spec';
import { By } from '@angular/platform-browser';
import { ImageField } from '../item-page-field.component';

let component: ItemPageImgFieldComponent;
let fixture: ComponentFixture<ItemPageImgFieldComponent>;

const mockField = 'organization.identifier.ror';
const mockValue = 'http://ror.org/awesome-identifier';
const mockLabel = 'ROR label';
const mockUrlRegex = '(.*)ror.org';
const mockImg = {
URI: './assets/images/ror-icon.svg',
alt: 'item.page.image.alt.ROR',
heightVar: '--ds-item-page-img-field-ror-inline-height'
} as ImageField;

describe('ItemPageImgFieldComponent', () => {

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
}
})],
providers: [
{ provide: APP_CONFIG, useValue: environment },
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
],
declarations: [ItemPageImgFieldComponent, GenericItemPageFieldComponent, MetadataValuesComponent],
schemas: [NO_ERRORS_SCHEMA]
})
.overrideComponent(GenericItemPageFieldComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
})
.compileComponents();

fixture = TestBed.createComponent(ItemPageImgFieldComponent);
component = fixture.componentInstance;
component.item = mockItemWithMetadataFieldsAndValue([mockField], mockValue);
component.fields = [mockField];
component.label = mockLabel;
component.urlRegex = mockUrlRegex;
component.img = mockImg;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should display display img tag', () => {
const image = fixture.debugElement.query(By.css('img.link-logo'));
expect(image).not.toBeNull();
});

it('should have right attributes', () => {
const image = fixture.debugElement.query(By.css('img.link-logo'));
expect(image.attributes.src).toEqual(mockImg.URI);
expect(image.attributes.alt).toEqual(mockImg.alt);

const imageEl = image.nativeElement;
expect(imageEl.style.height).toContain(mockImg.heightVar);
});

it('should have the right value', () => {
const imageAnchor = fixture.debugElement.query(By.css('a.link-anchor'));
const anchorEl = imageAnchor.nativeElement;
expect(anchorEl.innerHTML).toContain(mockValue);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Component, Input } from '@angular/core';
import { ImageField, ItemPageFieldComponent } from '../item-page-field.component';
import { Item } from '../../../../../core/shared/item.model';

@Component({
selector: 'ds-item-page-img-field',
templateUrl: '../item-page-field.component.html'
})
/**
* Component that renders an inline image for a given field.
* This component uses a given {@code ImageField} configuration to correctly render the img.
*/
export class ItemPageImgFieldComponent extends ItemPageFieldComponent {

/**
* The item to display metadata for
*/
@Input() item: Item;

/**
* Separator string between multiple values of the metadata fields defined
* @type {string}
*/
@Input() separator: string;

/**
* Fields (schema.element.qualifier) used to render their values.
*/
@Input() fields: string[];

/**
* Label i18n key for the rendered metadata
*/
@Input() label: string;

/**
* Image Configuration
*/
@Input() img: ImageField;

/**
* Whether any valid HTTP(S) URL should be rendered as a link
*/
@Input() urlRegex?: string;

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
[enableMarkdown]="enableMarkdown"
[urlRegex]="urlRegex"
[browseDefinition]="browseDefinition|async"
[img]="img"
></ds-metadata-values>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ import { BrowseDefinition } from '../../../../core/shared/browse-definition.mode
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
import { getRemoteDataPayload } from '../../../../core/shared/operators';

/**
* Interface that encapsulate Image configuration for this component.
*/
export interface ImageField {
/**
* URI that is used to retrieve the image.
*/
URI: string;
/**
* i18n Key that represents the alt text to display
*/
alt: string;
/**
* CSS variable that contains the height of the inline image.
*/
heightVar: string;
}


/**
* This component can be used to represent metadata on a simple item page.
* It expects one input parameter of type Item to which the metadata belongs.
Expand Down Expand Up @@ -51,6 +70,11 @@ export class ItemPageFieldComponent {
*/
urlRegex?: string;

/**
* Image Configuration
*/
img: ImageField;

/**
* Return browse definition that matches any field used in this component if it is configured as a browse
* link in dspace.cfg (webui.browse.link.<n>)
Expand Down
Loading

0 comments on commit e1c639e

Please sign in to comment.