Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ROR Integration - Identifier Visualization #2719

Merged
merged 9 commits into from
Feb 14, 2024
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
Loading