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

Make the CC license field component configurable in DSpace 8.0 #3165

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions config/config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,14 @@ liveRegion:
messageTimeOutDurationMs: 30000
# The visibility of the live region. Setting this to true is only useful for debugging purposes.
isVisible: false

# Creative Commons metadata fields
ccLicense:
# Icon variant:
# 'full' variant shows image, a disclaimer (optional) and name (always), better for the item page content.
# 'small' (default) variant shows image and name (optional), better for the item page sidebar
variant: small
# Shows the CC license name with the image. Always show if image fails to load
showName: true
# Shows the disclaimer in the 'full' variant of the component
showDisclaimer: true
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<div *ngIf="uri && name" class="item-page-field">
<div *ngIf="(uri$ | async) && (name$ | async)" class="item-page-field">
<ds-metadata-field-wrapper [label]="'item.page.cc.license.title' | translate">
<div [ngClass]="{'row': variant === 'full', 'col': variant === 'small'}">

<!-- 'img' tag is not rendered if any errors occurs when loading it -->
<div *ngIf="showImage" [ngClass]="{'col-auto': variant === 'full', 'row': variant === 'small'}"
style="align-content: center;"
>
<a [href]="uri" target="_blank" class="link-anchor dont-break-out ds-simple-metadata-link">
<img (error)="showImage = false" [src]="imgSrc" [alt]="name" class="cc-image"
<a [href]="uri$ | async" target="_blank" class="link-anchor dont-break-out ds-simple-metadata-link">
<img (error)="showImage = false" [src]="imgSrc$ | async" [alt]="name$ | async" class="cc-image"
[ngStyle]="{
'width': 'var(--ds-thumbnail-max-width)',
'margin-bottom': variant === 'small'? '1ch' : '0',
Expand All @@ -20,7 +20,7 @@
<div [ngClass]="{ 'row': variant === 'small', 'col': variant === 'full' }">
<span>
{{ variant === 'full' && showDisclaimer ? ('item.page.cc.license.disclaimer' | translate) : '' }}
<a *ngIf="showName || !showImage" [href]="uri" target="_blank" id="cc-name">{{ name }}</a>
<a *ngIf="showName || !showImage" [href]="uri$ | async" target="_blank" id="cc-name">{{ (name$ | async) }}</a>
</span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import {
TranslateLoader,
TranslateModule,
} from '@ngx-translate/core';
import { ConfigurationDataService } from 'src/app/core/data/configuration-data.service';
import { ConfigurationProperty } from 'src/app/core/shared/configuration-property.model';
import { Item } from 'src/app/core/shared/item.model';
import {
MetadataMap,
MetadataValue,
} from 'src/app/core/shared/metadata.models';
import { createSuccessfulRemoteDataObject$ } from 'src/app/shared/remote-data.utils';
import { ConfigurationDataServiceStub } from 'src/app/shared/testing/configuration-data.service.stub';
import { createPaginatedList } from 'src/app/shared/testing/utils.test';

import { APP_CONFIG } from '../../../../../../config/app-config.interface';
Expand Down Expand Up @@ -202,8 +205,22 @@ function configureFixture(

describe('ItemPageCcLicenseFieldComponent', () => {
let fixture: ComponentFixture<ItemPageCcLicenseFieldComponent>;
let configurationDataService = new ConfigurationDataServiceStub();

beforeEach(waitForAsync(() => {
configurationDataService.findByPropertyName = jasmine.createSpy()
.withArgs('cc.license.name').and.returnValue(createSuccessfulRemoteDataObject$({
... new ConfigurationProperty(),
name: 'cc.license.name',
values: [ 'dc.rights' ],
},
))
.withArgs('cc.license.uri').and.returnValue(createSuccessfulRemoteDataObject$({
... new ConfigurationProperty(),
name: 'cc.license.uri',
values: [ 'dc.rights.uri' ],
},
));
void TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({
Expand All @@ -214,7 +231,10 @@ describe('ItemPageCcLicenseFieldComponent', () => {
}),
ItemPageCcLicenseFieldComponent,
],
providers: [{ provide: APP_CONFIG, useValue: environment }],
providers: [
{ provide: APP_CONFIG, useValue: environment },
{ provide: ConfigurationDataService, useValue: configurationDataService },
],
schemas: [NO_ERRORS_SCHEMA],
})
.overrideComponent(ItemPageCcLicenseFieldComponent, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,46 @@
import {
AsyncPipe,
NgClass,
NgIf,
NgStyle,
} from '@angular/common';
import {
Component,
Inject,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import {
Observable,
of,
Subscription,
} from 'rxjs';
import { ConfigurationDataService } from 'src/app/core/data/configuration-data.service';
import { ConfigurationProperty } from 'src/app/core/shared/configuration-property.model';
import { Item } from 'src/app/core/shared/item.model';
import {
getFirstCompletedRemoteData,
getRemoteDataPayload,
} from 'src/app/core/shared/operators';
import { MetadataFieldWrapperComponent } from 'src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component';
import {
APP_CONFIG,
AppConfig,
} from 'src/config/app-config.interface';

@Component({
selector: 'ds-item-page-cc-license-field',
templateUrl: './item-page-cc-license-field.component.html',
standalone: true,
imports: [NgIf, NgClass, NgStyle, TranslateModule, MetadataFieldWrapperComponent],
imports: [AsyncPipe, NgIf, NgClass, NgStyle, TranslateModule, MetadataFieldWrapperComponent],
})
/**
* Displays the item's Creative Commons license image in it's simple item page
*/
export class ItemPageCcLicenseFieldComponent implements OnInit {
export class ItemPageCcLicenseFieldComponent implements OnInit, OnDestroy {

/**
* The item to display the CC license image for
*/
Expand All @@ -31,43 +50,83 @@ export class ItemPageCcLicenseFieldComponent implements OnInit {
* 'full' variant shows image, a disclaimer (optional) and name (always), better for the item page content.
* 'small' variant shows image and name (optional), better for the item page sidebar
*/
@Input() variant?: 'small' | 'full' = 'small';
@Input() variant?: 'small' | 'full' = this.appConfig.ccLicense.variant;

/**
* Filed name containing the CC license URI, as configured in the back-end, in the 'dspace.cfg' file, property
* 'cc.license.uri'
* Field name containing the CC license URI
*/
@Input() ccLicenseUriField? = 'dc.rights.uri';
@Input() ccLicenseUriField?;

/**
* Filed name containing the CC license name, as configured in the back-end, in the 'dspace.cfg' file, property
* 'cc.license.name'
* Field name containing the CC license name
*/
@Input() ccLicenseNameField? = 'dc.rights';
@Input() ccLicenseNameField?;

/**
* Shows the CC license name with the image. Always show if image fails to load
*/
@Input() showName? = true;
@Input() showName? = this.appConfig.ccLicense.showName;

/**
* Shows the disclaimer in the 'full' variant of the component
*/
@Input() showDisclaimer? = true;
@Input() showDisclaimer? = this.appConfig.ccLicense.showDisclaimer;

uri: string;
name: string;

subscriptions: Subscription[] = [];
showImage = true;
imgSrc: string;

ngOnInit() {
this.uri = this.item.firstMetadataValue(this.ccLicenseUriField);
this.name = this.item.firstMetadataValue(this.ccLicenseNameField);
uri$: Observable<string>;
name$: Observable<string>;
imgSrc$: Observable<string>;

constructor(
@Inject(APP_CONFIG) protected appConfig: AppConfig,
protected configService: ConfigurationDataService,
) {
}

// Extracts the CC license code from the URI
/**
* Parse a URI an return its CC code. URIs pointing to non-CC licenses will return null.
* @param uri
* @returns the CC code or null if uri is not a valid CC URI
*/
public static parseCcCode(uri: string): string {
const regex = /.*creativecommons.org\/(licenses|publicdomain)\/([^/]+)/gm;
const matches = regex.exec(this.uri ?? '') ?? [];
const ccCode = matches.length > 2 ? matches[2] : null;
this.imgSrc = ccCode ? `assets/images/cc-licenses/${ccCode}.png` : null;
const matches = regex.exec(uri ?? '') ?? [];
return matches.length > 2 ? matches[2] : null;
}

ngOnDestroy(): void {
this.subscriptions.forEach((sub: Subscription) => sub.unsubscribe());
}

ngOnInit() {
this.subscriptions.push(this.configService.findByPropertyName('cc.license.uri').pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
).subscribe((remoteData: ConfigurationProperty) => {
if (this.ccLicenseUriField === undefined) {
// Set the value only if it has not manually set when declaring this component
this.ccLicenseUriField = remoteData?.values && remoteData?.values?.length > 0 ? remoteData.values[0] : 'dc.rights.uri';
}
const uri = this.item.firstMetadataValue(this.ccLicenseUriField);
const ccCode = ItemPageCcLicenseFieldComponent.parseCcCode(uri);
this.uri$ = of(uri);
this.imgSrc$ = of(ccCode ? `assets/images/cc-licenses/${ccCode}.png` : null);
}),
);

this.subscriptions.push(this.configService.findByPropertyName('cc.license.name').pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
).subscribe((remoteData: ConfigurationProperty) => {
if (this.ccLicenseNameField === undefined) {
// Set the value only if it has not manually set when declaring this component
this.ccLicenseNameField = remoteData?.values && remoteData?.values?.length > 0 ? remoteData.values[0] : 'dc.rights';
}
this.name$ = of(this.item.firstMetadataValue(this.ccLicenseNameField));
}),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
Observable,
of,
} from 'rxjs';
import { ConfigurationDataService } from 'src/app/core/data/configuration-data.service';
import { ConfigurationDataServiceStub } from 'src/app/shared/testing/configuration-data.service.stub';

import { APP_CONFIG } from '../../../../../config/app-config.interface';
import { environment } from '../../../../../environments/environment.test';
Expand Down Expand Up @@ -88,6 +90,7 @@ function getItem(metadata: MetadataMap) {
describe('UntypedItemComponent', () => {
let comp: UntypedItemComponent;
let fixture: ComponentFixture<UntypedItemComponent>;
let configurationDataService = new ConfigurationDataServiceStub();

beforeEach(waitForAsync(() => {
const mockBitstreamDataService = {
Expand Down Expand Up @@ -130,6 +133,7 @@ describe('UntypedItemComponent', () => {
{ provide: ItemVersionsSharedService, useValue: {} },
{ provide: RouteService, useValue: mockRouteService },
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
{ provide: ConfigurationDataService, useValue: configurationDataService },
{ provide: APP_CONFIG, useValue: environment },
],
schemas: [NO_ERRORS_SCHEMA],
Expand Down
2 changes: 2 additions & 0 deletions src/config/app-config.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CollectionPageConfig } from './collection-page-config.interface';
import { CommunityListConfig } from './community-list-config.interface';
import { CommunityPageConfig } from './community-page-config.interface';
import { Config } from './config.interface';
import { CreativeCommonsLicenseConfig } from './creative-commons-license-config.interface';
import { DiscoverySortConfig } from './discovery-sort.config';
import { FilterVocabularyConfig } from './filter-vocabulary-config';
import { FormConfig } from './form-config.interfaces';
Expand Down Expand Up @@ -66,6 +67,7 @@ interface AppConfig extends Config {
search: SearchConfig;
notifyMetrics: AdminNotifyMetricsRow[];
liveRegion: LiveRegionConfig;
ccLicense: CreativeCommonsLicenseConfig;
}

/**
Expand Down
22 changes: 22 additions & 0 deletions src/config/creative-commons-license-config.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Config } from './config.interface';

export interface CreativeCommonsLicenseConfig extends Config {

/**
* CC icon variant ('small' or 'full')
* Used by {@link ItemPageCcLicenseFieldComponent}.
*/
variant: 'small' | 'full';

/**
* Shows the CC license name with the image. Always show if image fails to load
* Used by {@link ItemPageCcLicenseFieldComponent}.
*/
showName: boolean;

/**
* Show the disclaimer in the 'full' variant of the component
* Used by {@link ItemPageCcLicenseFieldComponent}.
*/
showDisclaimer: boolean;
}
8 changes: 8 additions & 0 deletions src/config/default-app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CacheConfig } from './cache-config.interface';
import { CollectionPageConfig } from './collection-page-config.interface';
import { CommunityListConfig } from './community-list-config.interface';
import { CommunityPageConfig } from './community-page-config.interface';
import { CreativeCommonsLicenseConfig } from './creative-commons-license-config.interface';
import { DiscoverySortConfig } from './discovery-sort.config';
import { FilterVocabularyConfig } from './filter-vocabulary-config';
import { FormConfig } from './form-config.interfaces';
Expand Down Expand Up @@ -598,4 +599,11 @@ export class DefaultAppConfig implements AppConfig {
messageTimeOutDurationMs: 30000,
isVisible: false,
};

// Metadatafields to determine the CC license variant
ccLicense: CreativeCommonsLicenseConfig = {
variant: 'small',
showName: true,
showDisclaimer: true,
};
}
6 changes: 6 additions & 0 deletions src/environments/environment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,4 +427,10 @@ export const environment: BuildConfig = {
messageTimeOutDurationMs: 30000,
isVisible: false,
},

ccLicense: {
variant: 'small',
showName: true,
showDisclaimer: true,
},
};
Loading