Skip to content

Commit 578a106

Browse files
authored
fix(attachments): fix PDF display and enable image/PDF opening in new tab (#1646)
* feat: enhance attachment handling and styling in dataset detail and file uploader components * remove empty space in dataset details page and added unit test for the change * added e2e test * remove .only * feat: add max file upload size configuration to app settings and update file uploader component * implement AttachmentService for handling file attachments in dataset detail and file uploader components * fix unit test fail * fix failing unit test * fix failing test * fix failing test
1 parent 105552a commit 578a106

17 files changed

+376
-67
lines changed

cypress/e2e/datasets/datasets-attachment.cy.js

+34
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,40 @@ describe("Dataset attachments", () => {
5050
);
5151
});
5252

53+
it("should open a new tab when clicking the attachment thumbnail", () => {
54+
cy.visit("/datasets");
55+
56+
cy.get(".dataset-table mat-table mat-header-row").should("exist");
57+
58+
cy.finishedLoading();
59+
60+
cy.get('[data-cy="text-search"] input[type="search"]')
61+
.clear()
62+
.type("Cypress");
63+
64+
cy.isLoading();
65+
66+
cy.get("mat-row").contains("Cypress Dataset").first().click();
67+
68+
cy.isLoading();
69+
70+
cy.get(".mat-mdc-tab-link").contains("Attachments").click();
71+
72+
cy.window().then((win) => {
73+
cy.stub(win, "open").as("open");
74+
});
75+
76+
cy.get('[data-cy="attachment-thumbnail"]').click();
77+
78+
cy.get("@open").should("be.calledWith", Cypress.sinon.match(/blob:.*/));
79+
80+
cy.get(".mat-mdc-tab-link").contains("Details").click();
81+
82+
cy.get('[data-cy="attachment-thumbnail"]').click();
83+
84+
cy.get("@open").should("be.calledWith", Cypress.sinon.match(/blob:.*/));
85+
});
86+
5387
it("should be able to download dataset attachment", () => {
5488
cy.visit("/datasets");
5589

src/app/app-config.service.spec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const appConfig: AppConfig = {
3838
logbookEnabled: true,
3939
loginFormEnabled: true,
4040
thumbnailFetchLimitPerPage: 500,
41+
maxFileUploadSizeInMb: "16mb",
4142
maxDirectDownloadSize: 5000000000,
4243
metadataPreviewEnabled: true,
4344
metadataStructure: "",

src/app/app-config.service.ts

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export interface AppConfig {
9898
defaultDatasetsListSettings: DatasetsListSettings;
9999
labelMaps: LabelMaps;
100100
thumbnailFetchLimitPerPage: number;
101+
maxFileUploadSizeInMb?: string;
101102
}
102103

103104
@Injectable()

src/app/datasets/dataset-detail/dataset-detail.component.html

+16-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
</div>
2525
<div style="clear: both"></div>
2626
<div fxLayout="row" fxLayout.xs="column" *ngIf="dataset">
27-
<div fxFlex="80%">
27+
<div [fxFlex]="(attachments$ | async)?.length > 0 ? '90%' : '100%'">
2828
<mat-card [formGroup]="form" data-cy="general-info">
2929
<mat-card-header class="general-header">
3030
<div mat-card-avatar class="section-icon">
@@ -437,7 +437,7 @@
437437

438438
<br />
439439

440-
<div *ngIf="show">
440+
<div *ngIf="show" class="jsonViewOverFlow">
441441
<ngx-json-viewer
442442
[json]="datasetWithout$ | async"
443443
[expanded]="false"
@@ -448,11 +448,22 @@
448448
</mat-card>
449449
</div>
450450

451-
<div fxFlex="30%" *ngIf="attachments$ | async as attachments">
451+
<div
452+
[fxFlex]="(attachments$ | async)?.length > 0 ? '10%' : '0'"
453+
*ngIf="attachments$ | async as attachments"
454+
>
452455
<ng-container *ngFor="let da of attachments">
453456
<mat-card>
454-
<img mat-card-image src="{{ da.thumbnail }}" />
455-
<mat-card-actions>{{ da.caption }}</mat-card-actions>
457+
<img
458+
data-cy="attachment-thumbnail"
459+
mat-card-image
460+
[src]="getImageUrl(da.thumbnail)"
461+
(click)="openAttachment(da.thumbnail)"
462+
class="thumbnail-image"
463+
/>
464+
<mat-card-actions class="caption-text">{{
465+
da.caption
466+
}}</mat-card-actions>
456467
</mat-card>
457468
</ng-container>
458469
</div>

src/app/datasets/dataset-detail/dataset-detail.component.scss

+13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ mat-card {
99
vertical-align: middle;
1010
}
1111
}
12+
.caption-text {
13+
word-break: break-all;
14+
}
1215

1316
table {
1417
th {
@@ -52,3 +55,13 @@ mat-card {
5255
margin: 1em 1em 0 0;
5356
float: right;
5457
}
58+
.attachment-card {
59+
display: flex;
60+
align-items: center;
61+
}
62+
.thumbnail-image {
63+
cursor: pointer;
64+
max-width: 14em; /* Set maximum width */
65+
width: 100%; /* Make it responsive within the card */
66+
height: auto; /* Maintain aspect ratio */
67+
}

src/app/datasets/dataset-detail/dataset-detail.component.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { ActivatedRoute, Router } from "@angular/router";
3838
import { MockActivatedRoute } from "shared/MockStubs";
3939
import { DialogComponent } from "shared/modules/dialog/dialog.component";
4040
import { AppConfigService } from "app-config.service";
41+
import { AttachmentService } from "shared/services/attachment.service";
4142

4243
describe("DatasetDetailComponent", () => {
4344
let component: DatasetDetailComponent;
@@ -70,6 +71,7 @@ describe("DatasetDetailComponent", () => {
7071
SharedScicatFrontendModule,
7172
StoreModule.forRoot({}),
7273
],
74+
providers: [AttachmentService],
7375
declarations: [DatasetDetailComponent, DatafilesComponent, LinkyPipe],
7476
});
7577
TestBed.overrideComponent(DatasetDetailComponent, {

src/app/datasets/dataset-detail/dataset-detail.component.ts

+17
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
} from "@angular/forms";
4545
import { Message, MessageType } from "state-management/models";
4646
import { DOCUMENT } from "@angular/common";
47+
import { AttachmentService } from "shared/services/attachment.service";
4748

4849
/**
4950
* Component to show details for a data set, using the
@@ -88,6 +89,7 @@ export class DatasetDetailComponent
8889
constructor(
8990
@Inject(DOCUMENT) private document: Document,
9091
public appConfigService: AppConfigService,
92+
private attachmentService: AttachmentService,
9193
public dialog: MatDialog,
9294
private store: Store,
9395
private router: Router,
@@ -345,4 +347,19 @@ export class DatasetDetailComponent
345347
);
346348
this.store.dispatch(showMessageAction({ message }));
347349
}
350+
base64MimeType(encoded: string): string {
351+
return this.attachmentService.base64MimeType(encoded);
352+
}
353+
354+
getImageUrl(encoded: string) {
355+
const mimeType = this.base64MimeType(encoded);
356+
if (mimeType === "application/pdf") {
357+
return "assets/images/pdf-icon.svg";
358+
}
359+
return encoded;
360+
}
361+
362+
openAttachment(encoded: string) {
363+
this.attachmentService.openAttachment(encoded);
364+
}
348365
}

src/app/datasets/dataset-file-uploader/dataset-file-uploader.component.spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,16 @@ import {
1212

1313
import { DatasetFileUploaderComponent } from "./dataset-file-uploader.component";
1414
import { SharedScicatFrontendModule } from "shared/shared.module";
15+
import { AppConfigService } from "app-config.service";
16+
import { HttpClient } from "@angular/common/http";
17+
import { MockHttp } from "shared/MockStubs";
18+
1519
const router = {
1620
navigateByUrl: jasmine.createSpy("navigateByUrl"),
1721
};
22+
23+
const getConfig = () => ({});
24+
1825
describe("DatasetFileUploaderComponent", () => {
1926
let component: DatasetFileUploaderComponent;
2027
let fixture: ComponentFixture<DatasetFileUploaderComponent>;
@@ -25,6 +32,10 @@ describe("DatasetFileUploaderComponent", () => {
2532
await TestBed.configureTestingModule({
2633
declarations: [DatasetFileUploaderComponent],
2734
imports: [SharedScicatFrontendModule, StoreModule.forRoot({})],
35+
providers: [
36+
{ provide: AppConfigService, useValue: { getConfig } },
37+
{ provide: HttpClient, useClass: MockHttp },
38+
],
2839
}).compileComponents();
2940
TestBed.overrideComponent(DatasetFileUploaderComponent, {
3041
set: {

src/app/datasets/datasets.module.ts

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import { DatasetsFilterSettingsComponent } from "./datasets-filter/settings/data
8989
import { CdkDrag, CdkDragHandle, CdkDropList } from "@angular/cdk/drag-drop";
9090
import { FiltersModule } from "shared/modules/filters/filters.module";
9191
import { userReducer } from "state-management/reducers/user.reducer";
92+
import { AttachmentService } from "shared/services/attachment.service";
9293

9394
@NgModule({
9495
imports: [

src/app/shared/modules/file-uploader/file-uploader.component.html

+49-39
Original file line numberDiff line numberDiff line change
@@ -38,44 +38,54 @@
3838
</button>
3939
</mat-card-content>
4040
</mat-card>
41+
<div class="grid-container">
42+
<ng-template ngFor let-attachment [ngForOf]="attachments">
43+
<mat-card class="attachment-card">
44+
<mat-card-content class="attachment-content">
45+
<img
46+
data-cy="attachment-thumbnail"
47+
[src]="getImageUrl(attachment.thumbnail)"
48+
(click)="openAttachment(attachment.thumbnail)"
49+
/>
4150

42-
<ng-template ngFor let-attachment [ngForOf]="attachments">
43-
<mat-card class="attachment-card">
44-
<mat-card-content>
45-
<img src="{{ attachment.thumbnail }}" />
46-
47-
<form (submit)="onSubmitCaption(attachment.id, caption.value)">
48-
<mat-form-field>
49-
<mat-label>Caption</mat-label>
50-
<input
51-
matInput
52-
id="caption"
53-
value="{{ attachment.caption }}"
54-
#caption
55-
/>
56-
</mat-form-field>
57-
<button mat-button type="submit" color="primary" class="submit-button">
58-
Submit
51+
<form (submit)="onSubmitCaption(attachment.id, caption.value)">
52+
<mat-form-field>
53+
<mat-label>Caption</mat-label>
54+
<input
55+
matInput
56+
id="caption"
57+
value="{{ attachment.caption }}"
58+
#caption
59+
/>
60+
</mat-form-field>
61+
<button
62+
mat-stroked-button
63+
type="submit"
64+
color="primary"
65+
class="submit-button"
66+
>
67+
Submit
68+
</button>
69+
</form>
70+
</mat-card-content>
71+
<mat-card-actions>
72+
<button
73+
mat-button
74+
class="download-button"
75+
(click)="onDownloadAttachment(attachment)"
76+
>
77+
<mat-icon>download</mat-icon>
78+
Download
79+
</button>
80+
<button
81+
mat-button
82+
class="delete-button"
83+
(click)="onDeleteAttachment(attachment.id)"
84+
>
85+
<mat-icon>delete</mat-icon>
86+
Remove
5987
</button>
60-
</form>
61-
</mat-card-content>
62-
<mat-card-actions align="end">
63-
<button
64-
mat-button
65-
class="download-button"
66-
(click)="onDownloadAttachment(attachment)"
67-
>
68-
<mat-icon>download</mat-icon>
69-
Download
70-
</button>
71-
<button
72-
mat-button
73-
class="delete-button"
74-
(click)="onDeleteAttachment(attachment.id)"
75-
>
76-
<mat-icon>delete</mat-icon>
77-
Remove
78-
</button>
79-
</mat-card-actions>
80-
</mat-card>
81-
</ng-template>
88+
</mat-card-actions>
89+
</mat-card>
90+
</ng-template>
91+
</div>

src/app/shared/modules/file-uploader/file-uploader.component.scss

+21-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
1-
.attachment-card {
2-
width: 22em;
3-
float: left;
4-
margin: 1em 2em 1em 0;
1+
.grid-container {
2+
display: grid;
3+
margin-top: 2em;
4+
grid-template-columns: repeat(auto-fill, minmax(14em, 1fr));
5+
gap: 1em;
6+
}
57

8+
.grid-item {
9+
display: flex;
10+
flex-direction: column; /* Align content vertically */
11+
height: 100%; /* Ensures the card takes full height */
12+
}
13+
14+
.attachment-card {
615
img {
7-
width: 20em;
8-
height: 20em;
16+
cursor: pointer;
17+
width: 12em;
18+
height: 14em;
919
}
1020

1121
button {
12-
float: right;
22+
width: 100%; /* Makes the button fill the width of the parent container */
23+
display: flex; /* Use Flexbox for centering */
24+
justify-content: center; /* Center the text horizontally */
25+
align-items: center; /* Center the text vertically */
1326
}
1427

1528
.submit-button {
@@ -43,8 +56,7 @@
4356
border: dashed 1px grey;
4457
display: flex;
4558
justify-content: center;
46-
height: 350px;
47-
width: 500px;
59+
height: 14em;
4860

4961
input {
5062
display: none;

0 commit comments

Comments
 (0)