Skip to content

Commit 64e866e

Browse files
[DURACOM-455] add download button
1 parent 5bd912c commit 64e866e

14 files changed

Lines changed: 241 additions & 20 deletions

File tree

src/app/item-page/full/field-components/file-section/full-file-section.component.html

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,16 @@ <h3 class="h5 simple-view-element-header">
4040
</dl>
4141
</div>
4242
<div class="col-2">
43-
<ds-file-download-link [showIcon]="true" [bitstream]="file" [item]="item" cssClasses="btn btn-outline-primary btn-download">
44-
<span class="d-none d-md-inline">
45-
{{ "item.page.filesection.download" | translate }}
46-
</span>
47-
</ds-file-download-link>
43+
@if (showLinkAsButton) {
44+
<ds-file-download-button [bitstream]="file" [item]="item"></ds-file-download-button>
45+
} @else {
46+
<ds-file-download-link [showIcon]="true" [bitstream]="file" [item]="item" cssClasses="btn btn-outline-primary btn-download">
47+
<span class="d-none d-md-inline">
48+
{{ "item.page.filesection.download" | translate }}
49+
</span>
50+
</ds-file-download-link>
51+
}
52+
4853
</div>
4954
</div>
5055
}
@@ -92,11 +97,15 @@ <h3 class="h5 simple-view-element-header">
9297
</dl>
9398
</div>
9499
<div class="col-2">
95-
<ds-file-download-link [showIcon]="true" [bitstream]="file" [item]="item" cssClasses="btn btn-outline-primary btn-download">
96-
<span class="d-none d-md-inline">
97-
{{ "item.page.filesection.download" | translate }}
98-
</span>
99-
</ds-file-download-link>
100+
@if (showLinkAsButton) {
101+
<ds-file-download-button [bitstream]="file" [item]="item"></ds-file-download-button>
102+
} @else {
103+
<ds-file-download-link [showIcon]="true" [bitstream]="file" [item]="item" cssClasses="btn btn-outline-primary btn-download">
104+
<span class="d-none d-md-inline">
105+
{{ "item.page.filesection.download" | translate }}
106+
</span>
107+
</ds-file-download-link>
108+
}
100109
</div>
101110
</div>
102111
}

src/app/item-page/full/field-components/file-section/full-file-section.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
tap,
3535
} from 'rxjs/operators';
3636

37+
import { FileDownloadButtonComponent } from '../../../../shared/file-download-button/file-download-button.component';
3738
import { ThemedFileDownloadLinkComponent } from '../../../../shared/file-download-link/themed-file-download-link.component';
3839
import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
3940
import { PaginationComponent } from '../../../../shared/pagination/pagination.component';
@@ -53,6 +54,7 @@ import { FileSectionComponent } from '../../../simple/field-components/file-sect
5354
templateUrl: './full-file-section.component.html',
5455
imports: [
5556
AsyncPipe,
57+
FileDownloadButtonComponent,
5658
FileSizePipe,
5759
MetadataFieldWrapperComponent,
5860
PaginationComponent,

src/app/item-page/simple/field-components/file-section/file-section.component.html

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,37 @@
33
<ds-metadata-field-wrapper [label]="label | translate">
44
<div class="file-section">
55
@for (file of bitstreams; track file; let last = $last) {
6-
<ds-file-download-link [bitstream]="file" [item]="item">
7-
<span>
8-
@if (primaryBitstreamId === file.id) {
9-
<span class="badge bg-primary">{{ 'item.page.bitstreams.primary' | translate }}</span>
10-
}
11-
{{ dsoNameService.getName(file) }}
12-
</span>
13-
<span> ({{(file?.sizeBytes) | dsFileSize }})</span>
6+
<ng-template #fileContent>
7+
<span>
8+
@if (primaryBitstreamId === file.id) {
9+
<span class="badge bg-primary">
10+
{{ 'item.page.bitstreams.primary' | translate }}
11+
</span>
12+
}
13+
{{ dsoNameService.getName(file) }}
14+
</span>
15+
16+
<span>({{ file?.sizeBytes | dsFileSize }})</span>
17+
1418
@if (!last) {
15-
<span innerHTML="{{separator}}"></span>
19+
<span [innerHTML]="separator"></span>
1620
}
17-
</ds-file-download-link>
21+
</ng-template>
22+
23+
@if (showLinkAsButton) {
24+
<ds-file-download-button
25+
[bitstream]="file"
26+
[item]="item">
27+
<ng-container *ngTemplateOutlet="fileContent"></ng-container>
28+
</ds-file-download-button>
29+
} @else {
30+
<ds-file-download-link
31+
[bitstream]="file"
32+
[item]="item">
33+
<ng-container *ngTemplateOutlet="fileContent"></ng-container>
34+
</ds-file-download-link>
35+
}
36+
1837
}
1938
@if (isLoading) {
2039
<ds-loading message="{{'loading.default' | translate}}" [showMessage]="false"></ds-loading>

src/app/item-page/simple/field-components/file-section/file-section.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
} from '@ngx-translate/core';
2626
import { BehaviorSubject } from 'rxjs';
2727

28+
import { FileDownloadButtonComponent } from '../../../../shared/file-download-button/file-download-button.component';
2829
import { ThemedFileDownloadLinkComponent } from '../../../../shared/file-download-link/themed-file-download-link.component';
2930
import { ThemedLoadingComponent } from '../../../../shared/loading/themed-loading.component';
3031
import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
@@ -40,6 +41,7 @@ import { VarDirective } from '../../../../shared/utils/var.directive';
4041
templateUrl: './file-section.component.html',
4142
imports: [
4243
CommonModule,
44+
FileDownloadButtonComponent,
4345
FileSizePipe,
4446
MetadataFieldWrapperComponent,
4547
ThemedFileDownloadLinkComponent,
@@ -68,6 +70,8 @@ export class FileSectionComponent implements OnInit {
6870

6971
primaryBitstreamId: string;
7072

73+
showLinkAsButton: boolean;
74+
7175
constructor(
7276
protected bitstreamDataService: BitstreamDataService,
7377
protected notificationsService: NotificationsService,
@@ -76,6 +80,7 @@ export class FileSectionComponent implements OnInit {
7680
@Inject(APP_CONFIG) protected appConfig: AppConfig,
7781
) {
7882
this.pageSize = this.appConfig.item.bitstream.pageSize;
83+
this.showLinkAsButton = this.appConfig.layout.showDownloadLinkAsButton;
7984
}
8085

8186
ngOnInit(): void {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<span class="me-2">
2+
<ng-content></ng-content>
3+
</span>
4+
5+
@if (!hasNoDownload) {
6+
@if (bitstreamPath$ | async; as bitstreamLink) {
7+
@if (canDownload$ | async) {
8+
<button [routerLink]="bitstreamLink?.routerLink" [queryParams]="bitstreamLink?.queryParams" class="btn btn-outline-primary" data-test="download">
9+
<i class="fas fa-download"></i>
10+
<span class="d-none d-md-inline">{{ 'file-download-link.download' | translate }}</span>
11+
</button>
12+
} @else {
13+
<button [routerLink]="bitstreamLink?.routerLink"
14+
[queryParams]="bitstreamLink?.queryParams"
15+
[dsBtnDisabled]="(canRequestACopy$ | async) !== true"
16+
class="btn btn-outline-primary"
17+
data-test="requestACopy">
18+
{{ 'file-download-button.request-copy' | translate }}
19+
</button>
20+
}
21+
}
22+
}
23+

src/app/shared/file-download-button/file-download-button.component.scss

Whitespace-only changes.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import {
2+
ComponentFixture,
3+
TestBed,
4+
} from '@angular/core/testing';
5+
import { By } from '@angular/platform-browser';
6+
import { RouterTestingModule } from '@angular/router/testing';
7+
import { ConfigurationDataService } from '@dspace/core/data/configuration-data.service';
8+
import { ConfigurationProperty } from '@dspace/core/shared/configuration-property.model';
9+
import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock';
10+
import { createSuccessfulRemoteDataObject$ } from '@dspace/core/utilities/remote-data.utils';
11+
import {
12+
TranslateLoader,
13+
TranslateModule,
14+
} from '@ngx-translate/core';
15+
import { of } from 'rxjs';
16+
import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service';
17+
import { Bitstream } from 'src/app/core/shared/bitstream.model';
18+
import { Item } from 'src/app/core/shared/item.model';
19+
20+
import { FileDownloadButtonComponent } from './file-download-button.component';
21+
22+
describe('FileDownloadButtonComponent', () => {
23+
let component: FileDownloadButtonComponent;
24+
let fixture: ComponentFixture<FileDownloadButtonComponent>;
25+
26+
let authorizationService: AuthorizationDataService;
27+
28+
let bitstream: Bitstream;
29+
let item: Item;
30+
let configurationDataService: ConfigurationDataService;
31+
32+
function init() {
33+
authorizationService = jasmine.createSpyObj('authorizationService', {
34+
isAuthorized: jasmine.createSpy('isAuthorized'),
35+
});
36+
bitstream = Object.assign(new Bitstream(), {
37+
uuid: 'bitstreamUuid',
38+
_links: {
39+
self: { href: 'obj-selflink' },
40+
},
41+
});
42+
item = Object.assign(new Item(), {
43+
uuid: 'itemUuid',
44+
_links: {
45+
self: { href: 'obj-selflink' },
46+
},
47+
});
48+
configurationDataService = jasmine.createSpyObj('configurationDataService', {
49+
findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), {
50+
name: 'request.item.type',
51+
values: [],
52+
})),
53+
});
54+
}
55+
56+
57+
beforeEach(async () => {
58+
59+
init();
60+
61+
await TestBed.configureTestingModule({
62+
imports: [
63+
RouterTestingModule,
64+
TranslateModule.forRoot({
65+
loader: {
66+
provide: TranslateLoader,
67+
useClass: TranslateLoaderMock,
68+
},
69+
}),
70+
FileDownloadButtonComponent,
71+
],
72+
providers: [
73+
{ provide: AuthorizationDataService, useValue: authorizationService },
74+
{ provide: ConfigurationDataService, useValue: configurationDataService },
75+
],
76+
})
77+
.compileComponents();
78+
});
79+
beforeEach(() => {
80+
fixture = TestBed.createComponent(FileDownloadButtonComponent);
81+
component = fixture.componentInstance;
82+
component.bitstream = bitstream;
83+
component.item = item;
84+
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(of(true));
85+
component.bitstreamPath$ = of({
86+
routerLink: 'test',
87+
queryParams: {},
88+
});
89+
component.ngOnInit();
90+
fixture.detectChanges();
91+
});
92+
93+
it('should create', () => {
94+
expect(component).toBeTruthy();
95+
});
96+
97+
it('should show download button', () => {
98+
expect(fixture.debugElement.query(By.css('[data-test="download"]'))).toBeTruthy();
99+
expect(fixture.debugElement.query(By.css('[data-test="requestACopy"]'))).toBeFalsy();
100+
});
101+
102+
it('should show can request a copy button', () => {
103+
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(of(false));
104+
component.ngOnInit();
105+
fixture.detectChanges();
106+
expect(fixture.debugElement.query(By.css('[data-test="download"]'))).toBeFalsy();
107+
expect(fixture.debugElement.query(By.css('[data-test="requestACopy"]'))).toBeTruthy();
108+
});
109+
110+
it('should show a disabled can request a copy button when request.item.type has no value', () => {
111+
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(of(false));
112+
component.ngOnInit();
113+
fixture.detectChanges();
114+
const btn = fixture.debugElement.query(By.css('[data-test="requestACopy"]'));
115+
expect(btn.nativeNode.getAttribute('aria-disabled')).toBe('true');
116+
expect(btn.nativeNode.classList.contains('disabled')).toBeTrue();
117+
});
118+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { AsyncPipe } from '@angular/common';
2+
import {
3+
Component,
4+
OnInit,
5+
} from '@angular/core';
6+
import { RouterLink } from '@angular/router';
7+
import { TranslateModule } from '@ngx-translate/core';
8+
import { BtnDisabledDirective } from 'src/app/shared/btn-disabled.directive';
9+
10+
import { FileDownloadLinkComponent } from '../file-download-link/file-download-link.component';
11+
12+
13+
@Component({
14+
selector: 'ds-file-download-button',
15+
templateUrl: './file-download-button.component.html',
16+
styleUrls: ['./file-download-button.component.scss'],
17+
imports: [
18+
AsyncPipe,
19+
BtnDisabledDirective,
20+
RouterLink,
21+
TranslateModule,
22+
],
23+
})
24+
/**
25+
* Component displaying a download button or the request a copy button depending on authorization
26+
*/
27+
export class FileDownloadButtonComponent extends FileDownloadLinkComponent implements OnInit {
28+
29+
hasNoDownload = true;
30+
31+
ngOnInit() {
32+
super.ngOnInit();
33+
this.hasNoDownload = this.bitstream?.allMetadataValues('bitstream.viewer.provider').includes('nodownload');
34+
}
35+
36+
}

src/assets/i18n/en.json5

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7326,6 +7326,8 @@
73267326

73277327
"file-download-link.request-copy": "Request a copy of ",
73287328

7329+
"file-download-button.request-copy": "Request a copy",
7330+
73297331
"item.preview.organization.url": "URL",
73307332

73317333
"item.preview.organization.address.addressLocality": "City",

src/config/default-app-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,7 @@ export class DefaultAppConfig implements AppConfig {
721721
},
722722
},
723723
],
724+
showDownloadLinkAsButton: true,
724725
};
725726

726727
searchResult: SearchResultConfig = {

0 commit comments

Comments
 (0)