Merge pull request #2075 from atmire/w2p-98855_themeable-file-download-link_contribute-main

Pass `ng-content` to themed child components
This commit is contained in:
Tim Donohue
2023-02-14 12:33:54 -06:00
committed by GitHub
13 changed files with 94 additions and 31 deletions

View File

@@ -33,9 +33,9 @@
</dl> </dl>
</div> </div>
<div class="col-2"> <div class="col-2">
<ds-file-download-link [bitstream]="file" [item]="item"> <ds-themed-file-download-link [bitstream]="file" [item]="item">
{{"item.page.filesection.download" | translate}} {{"item.page.filesection.download" | translate}}
</ds-file-download-link> </ds-themed-file-download-link>
</div> </div>
</div> </div>
</ds-pagination> </ds-pagination>
@@ -74,9 +74,9 @@
</dl> </dl>
</div> </div>
<div class="col-2"> <div class="col-2">
<ds-file-download-link [bitstream]="file" [item]="item"> <ds-themed-file-download-link [bitstream]="file" [item]="item">
{{"item.page.filesection.download" | translate}} {{"item.page.filesection.download" | translate}}
</ds-file-download-link> </ds-themed-file-download-link>
</div> </div>
</div> </div>
</ds-pagination> </ds-pagination>

View File

@@ -1,11 +1,11 @@
<ng-container *ngVar="(bitstreams$ | async) as bitstreams"> <ng-container *ngVar="(bitstreams$ | async) as bitstreams">
<ds-metadata-field-wrapper *ngIf="bitstreams?.length > 0" [label]="label | translate"> <ds-metadata-field-wrapper *ngIf="bitstreams?.length > 0" [label]="label | translate">
<div class="file-section"> <div class="file-section">
<ds-file-download-link *ngFor="let file of bitstreams; let last=last;" [bitstream]="file" [item]="item"> <ds-themed-file-download-link *ngFor="let file of bitstreams; let last=last;" [bitstream]="file" [item]="item">
<span>{{file?.name}}</span> <span>{{file?.name}}</span>
<span>({{(file?.sizeBytes) | dsFileSize }})</span> <span>({{(file?.sizeBytes) | dsFileSize }})</span>
<span *ngIf="!last" innerHTML="{{separator}}"></span> <span *ngIf="!last" innerHTML="{{separator}}"></span>
</ds-file-download-link> </ds-themed-file-download-link>
<ds-themed-loading *ngIf="isLoading" message="{{'loading.default' | translate}}" [showMessage]="false"></ds-themed-loading> <ds-themed-loading *ngIf="isLoading" message="{{'loading.default' | translate}}" [showMessage]="false"></ds-themed-loading>
<div *ngIf="!isLastPage" class="mt-1" id="view-more"> <div *ngIf="!isLastPage" class="mt-1" id="view-more">
<a class="bitstream-view-more btn btn-outline-secondary btn-sm" [routerLink]="[]" (click)="getNextPage()">{{'item.page.bitstreams.view-more' | translate}}</a> <a class="bitstream-view-more btn btn-outline-secondary btn-sm" [routerLink]="[]" (click)="getNextPage()">{{'item.page.bitstreams.view-more' | translate}}</a>

View File

@@ -112,7 +112,7 @@ describe('FileSectionComponent', () => {
it('one bitstream should be on the page', () => { it('one bitstream should be on the page', () => {
const viewMore = fixture.debugElement.query(By.css('.bitstream-view-more')); const viewMore = fixture.debugElement.query(By.css('.bitstream-view-more'));
viewMore.triggerEventHandler('click', null); viewMore.triggerEventHandler('click', null);
const fileDownloadLink = fixture.debugElement.queryAll(By.css('ds-file-download-link')); const fileDownloadLink = fixture.debugElement.queryAll(By.css('ds-themed-file-download-link'));
expect(fileDownloadLink.length).toEqual(1); expect(fileDownloadLink.length).toEqual(1);
}); });
@@ -125,7 +125,7 @@ describe('FileSectionComponent', () => {
}); });
it('should contain another bitstream', () => { it('should contain another bitstream', () => {
const fileDownloadLink = fixture.debugElement.queryAll(By.css('ds-file-download-link')); const fileDownloadLink = fixture.debugElement.queryAll(By.css('ds-themed-file-download-link'));
expect(fileDownloadLink.length).toEqual(2); expect(fileDownloadLink.length).toEqual(2);
}); });
}); });

View File

@@ -17,10 +17,10 @@
<div *ngVar="(filesRD$ | async)?.payload?.page as files"> <div *ngVar="(filesRD$ | async)?.payload?.page as files">
<ds-process-detail-field *ngIf="files && files?.length > 0" id="process-files" <ds-process-detail-field *ngIf="files && files?.length > 0" id="process-files"
[title]="'process.detail.output-files'"> [title]="'process.detail.output-files'">
<ds-file-download-link *ngFor="let file of files; let last=last;" [bitstream]="file"> <ds-themed-file-download-link *ngFor="let file of files; let last=last;" [bitstream]="file">
<span>{{getFileName(file)}}</span> <span>{{getFileName(file)}}</span>
<span>({{(file?.sizeBytes) | dsFileSize }})</span> <span>({{(file?.sizeBytes) | dsFileSize }})</span>
</ds-file-download-link> </ds-themed-file-download-link>
</ds-process-detail-field> </ds-process-detail-field>
</div> </div>

View File

@@ -0,0 +1,38 @@
import { ThemedComponent } from '../theme-support/themed.component';
import { Component, Input } from '@angular/core';
import { FileDownloadLinkComponent } from './file-download-link.component';
import { Bitstream } from '../../core/shared/bitstream.model';
import { Item } from '../../core/shared/item.model';
@Component({
selector: 'ds-themed-file-download-link',
styleUrls: [],
templateUrl: '../theme-support/themed.component.html',
})
export class ThemedFileDownloadLinkComponent extends ThemedComponent<FileDownloadLinkComponent> {
@Input() bitstream: Bitstream;
@Input() item: Item;
@Input() cssClasses: string;
@Input() isBlank: boolean;
@Input() enableRequestACopy: boolean;
protected inAndOutputNames: (keyof FileDownloadLinkComponent & keyof this)[] = ['bitstream', 'item', 'cssClasses', 'isBlank', 'enableRequestACopy'];
protected getComponentName(): string {
return 'FileDownloadLinkComponent';
}
protected importThemedComponent(themeName: string): Promise<any> {
return import(`../../../themes/${themeName}/app/shared/file-download-link/file-download-link.component`);
}
protected importUnthemedComponent(): Promise<any> {
return import('./file-download-link.component');
}
}

View File

@@ -200,6 +200,7 @@ import { ClaimedTaskActionsDirective } from './mydspace-actions/claimed-task/swi
import { ImpersonateNavbarComponent } from './impersonate-navbar/impersonate-navbar.component'; import { ImpersonateNavbarComponent } from './impersonate-navbar/impersonate-navbar.component';
import { NgForTrackByIdDirective } from './ng-for-track-by-id.directive'; import { NgForTrackByIdDirective } from './ng-for-track-by-id.directive';
import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component'; import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component';
import { ThemedFileDownloadLinkComponent } from './file-download-link/themed-file-download-link.component';
import { CollectionDropdownComponent } from './collection-dropdown/collection-dropdown.component'; import { CollectionDropdownComponent } from './collection-dropdown/collection-dropdown.component';
import { EntityDropdownComponent } from './entity-dropdown/entity-dropdown.component'; import { EntityDropdownComponent } from './entity-dropdown/entity-dropdown.component';
import { CurationFormComponent } from '../curation-form/curation-form.component'; import { CurationFormComponent } from '../curation-form/curation-form.component';
@@ -413,6 +414,7 @@ const ENTRY_COMPONENTS = [
CollectionDropdownComponent, CollectionDropdownComponent,
ThemedCollectionDropdownComponent, ThemedCollectionDropdownComponent,
FileDownloadLinkComponent, FileDownloadLinkComponent,
ThemedFileDownloadLinkComponent,
CurationFormComponent, CurationFormComponent,
ExportMetadataSelectorComponent, ExportMetadataSelectorComponent,
ImportBatchSelectorComponent, ImportBatchSelectorComponent,

View File

@@ -1 +1,5 @@
<ng-template #vcr></ng-template> <ng-template #vcr>
</ng-template>
<div #content>
<ng-content></ng-content>
</div>

View File

@@ -9,7 +9,8 @@ import {
ComponentFactoryResolver, ComponentFactoryResolver,
ChangeDetectorRef, ChangeDetectorRef,
OnChanges, OnChanges,
HostBinding HostBinding,
ElementRef,
} from '@angular/core'; } from '@angular/core';
import { hasValue, isNotEmpty } from '../empty.util'; import { hasValue, isNotEmpty } from '../empty.util';
import { from as fromPromise, Observable, of as observableOf, Subscription, BehaviorSubject } from 'rxjs'; import { from as fromPromise, Observable, of as observableOf, Subscription, BehaviorSubject } from 'rxjs';
@@ -25,6 +26,7 @@ import { BASE_THEME_NAME } from './theme.constants';
}) })
export abstract class ThemedComponent<T> implements OnInit, OnDestroy, OnChanges { export abstract class ThemedComponent<T> implements OnInit, OnDestroy, OnChanges {
@ViewChild('vcr', { read: ViewContainerRef }) vcr: ViewContainerRef; @ViewChild('vcr', { read: ViewContainerRef }) vcr: ViewContainerRef;
@ViewChild('content') themedElementContent: ElementRef;
protected compRef: ComponentRef<T>; protected compRef: ComponentRef<T>;
/** /**
@@ -46,7 +48,7 @@ export abstract class ThemedComponent<T> implements OnInit, OnDestroy, OnChanges
constructor( constructor(
protected resolver: ComponentFactoryResolver, protected resolver: ComponentFactoryResolver,
protected cdr: ChangeDetectorRef, protected cdr: ChangeDetectorRef,
protected themeService: ThemeService protected themeService: ThemeService,
) { ) {
} }
@@ -102,10 +104,11 @@ export abstract class ThemedComponent<T> implements OnInit, OnDestroy, OnChanges
}), }),
).subscribe((constructor: GenericConstructor<T>) => { ).subscribe((constructor: GenericConstructor<T>) => {
const factory = this.resolver.resolveComponentFactory(constructor); const factory = this.resolver.resolveComponentFactory(constructor);
this.compRef = this.vcr.createComponent(factory); this.compRef = this.vcr.createComponent(factory, undefined, undefined, [this.themedElementContent.nativeElement.childNodes]);
this.connectInputsAndOutputs(); this.connectInputsAndOutputs();
this.compRef$.next(this.compRef); this.compRef$.next(this.compRef);
this.cdr.markForCheck(); this.cdr.markForCheck();
this.themedElementContent.nativeElement.remove();
}); });
} }
@@ -121,7 +124,7 @@ export abstract class ThemedComponent<T> implements OnInit, OnDestroy, OnChanges
protected connectInputsAndOutputs(): void { protected connectInputsAndOutputs(): void {
if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) {
this.inAndOutputNames.forEach((name: any) => { this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => {
this.compRef.instance[name] = this[name]; this.compRef.instance[name] = this[name];
}); });
} }

View File

@@ -10,9 +10,9 @@
</div> </div>
<div class="float-right w-15"> <div class="float-right w-15">
<ng-container> <ng-container>
<ds-file-download-link [cssClasses]="'btn btn-link-focus'" [isBlank]="true" [bitstream]="getBitstream()" [enableRequestACopy]="false"> <ds-themed-file-download-link [cssClasses]="'btn btn-link-focus'" [isBlank]="true" [bitstream]="getBitstream()" [enableRequestACopy]="false">
<i class="fa fa-download fa-2x text-normal" aria-hidden="true"></i> <i class="fa fa-download fa-2x text-normal" aria-hidden="true"></i>
</ds-file-download-link> </ds-themed-file-download-link>
<button class="btn btn-link-focus" <button class="btn btn-link-focus"
[attr.aria-label]="'submission.sections.upload.edit.title' | translate" [attr.aria-label]="'submission.sections.upload.edit.title' | translate"
title="{{ 'submission.sections.upload.edit.title' | translate }}" title="{{ 'submission.sections.upload.edit.title' | translate }}"

View File

@@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import {
FileDownloadLinkComponent as BaseComponent
} from '../../../../../app/shared/file-download-link/file-download-link.component';
@Component({
selector: 'ds-file-download-link',
// templateUrl: './file-download-link.component.html',
templateUrl: '../../../../../app/shared/file-download-link/file-download-link.component.html',
// styleUrls: ['./file-download-link.component.scss'],
styleUrls: ['../../../../../app/shared/file-download-link/file-download-link.component.scss'],
})
export class FileDownloadLinkComponent extends BaseComponent {
}

View File

@@ -45,6 +45,7 @@ import { CollectionDropdownComponent } from './app/shared/collection-dropdown/co
import { SharedBrowseByModule } from '../../app/shared/browse-by/shared-browse-by.module'; import { SharedBrowseByModule } from '../../app/shared/browse-by/shared-browse-by.module';
import { ResultsBackButtonModule } from '../../app/shared/results-back-button/results-back-button.module'; import { ResultsBackButtonModule } from '../../app/shared/results-back-button/results-back-button.module';
import { DsoPageModule } from '../../app/shared/dso-page/dso-page.module'; import { DsoPageModule } from '../../app/shared/dso-page/dso-page.module';
import { FileDownloadLinkComponent } from './app/shared/file-download-link/file-download-link.component';
/** /**
@@ -61,6 +62,7 @@ const ENTRY_COMPONENTS = [
CommunityListElementComponent, CommunityListElementComponent,
CollectionListElementComponent, CollectionListElementComponent,
CollectionDropdownComponent, CollectionDropdownComponent,
FileDownloadLinkComponent,
]; ];
const DECLARATIONS = [ const DECLARATIONS = [
@@ -80,21 +82,21 @@ const DECLARATIONS = [
]; ];
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
RootModule, RootModule,
NavbarModule, NavbarModule,
SharedBrowseByModule, SharedBrowseByModule,
ResultsBackButtonModule, ResultsBackButtonModule,
ItemPageModule, ItemPageModule,
ItemSharedModule, ItemSharedModule,
DsoPageModule, DsoPageModule,
], ],
declarations: DECLARATIONS, declarations: DECLARATIONS,
providers: [ providers: [
...ENTRY_COMPONENTS.map((component) => ({provide: component})) ...ENTRY_COMPONENTS.map((component) => ({provide: component}))
], ],
}) })
/** /**
* This module is included in the main bundle that gets downloaded at first page load. So it should * This module is included in the main bundle that gets downloaded at first page load. So it should