Fix: Improve footer responsiveness and download button on the full item page (#4218)

* fix: footer and download button responsiveness

* Revert styling changes to the download button on the simple item page

* fix: for restrict bitstrream show only lock icon

* ix: display download icon consistently across all item pages

* fix embargo label responsiveness

* Recommit without json5 changes

* Recommit without json5 changes

* Recommit without json5 changes
This commit is contained in:
Jesiel Viana
2025-05-06 14:21:18 -03:00
committed by GitHub
parent b15e9d74d6
commit 8757712905
11 changed files with 170 additions and 101 deletions

View File

@@ -49,8 +49,8 @@
<!-- Grid container --> <!-- Grid container -->
<!-- Copyright --> <!-- Copyright -->
<div class="bottom-footer p-1 d-flex justify-content-center align-items-center text-white"> <div class="bottom-footer p-1 d-flex flex-column flex-md-row justify-content-center align-items-center text-white">
<div class="content-container"> <div class="content-container align-self-center">
<p class="m-0"> <p class="m-0">
<a class="text-white" <a class="text-white"
href="http://www.dspace.org/" role="link" tabindex="0">{{ 'footer.link.dspace' | translate}}</a> href="http://www.dspace.org/" role="link" tabindex="0">{{ 'footer.link.dspace' | translate}}</a>
@@ -85,7 +85,7 @@
</ul> </ul>
</div> </div>
@if (coarLdnEnabled$ | async) { @if (coarLdnEnabled$ | async) {
<div class="notify-enabled text-white"> <div class="notify-enabled text-white align-self-end">
<a class="coar-notify-support-route" routerLink="info/coar-notify-support" role="link" tabindex="0"> <a class="coar-notify-support-route" routerLink="info/coar-notify-support" role="link" tabindex="0">
<img class="n-coar" src="assets/images/n-coar.svg" [attr.alt]="'menu.header.image.logo' | translate" /> <img class="n-coar" src="assets/images/n-coar.svg" [attr.alt]="'menu.header.image.logo' | translate" />
{{ 'footer.link.coar-notify-support' | translate }} {{ 'footer.link.coar-notify-support' | translate }}
@@ -94,4 +94,4 @@
} }
</div> </div>
<!-- Copyright --> <!-- Copyright -->
</footer> </footer>

View File

@@ -23,9 +23,8 @@
.bottom-footer { .bottom-footer {
.notify-enabled { .notify-enabled {
position: absolute; position: relative;
bottom: 4px; margin-top: 4px;
right: 0;
.coar-notify-support-route { .coar-notify-support-route {
padding: 0 calc(var(--bs-spacer) / 2); padding: 0 calc(var(--bs-spacer) / 2);
@@ -37,7 +36,11 @@
margin-bottom: 8.5px; margin-bottom: 8.5px;
} }
margin-top: 20px; @media screen and (min-width: map-get($grid-breakpoints, md)) {
position: absolute;
bottom: 4px;
right: 0;
}
} }
ul { ul {
li { li {

View File

@@ -1,84 +1,108 @@
<ds-metadata-field-wrapper [label]="label | translate"> <ds-metadata-field-wrapper [label]="label | translate">
<div *ngVar="(originals$ | async)?.payload as originals"> <div *ngVar="(originals$ | async)?.payload as originals">
@if (hasValuesInBundle(originals)) { @if (hasValuesInBundle(originals)) {
<div> <div>
<h3 class="h5 simple-view-element-header">{{"item.page.filesection.original.bundle" | translate}}</h3> <h3 class="h5 simple-view-element-header">
@if (originals?.page?.length > 0) { {{ "item.page.filesection.original.bundle" | translate }}
<ds-pagination </h3>
[hideGear]="true" @if (originals?.page?.length > 0) {
[hidePagerWhenSinglePage]="true" <ds-pagination [hideGear]="true" [hidePagerWhenSinglePage]="true" [paginationOptions]="originalOptions"
[paginationOptions]="originalOptions" [collectionSize]="originals?.totalElements" [retainScrollPosition]="true">
[collectionSize]="originals?.totalElements" @for (file of originals?.page; track file) {
[retainScrollPosition]="true"> <div class="file-section row mb-3">
@for (file of originals?.page; track file) { <div class="col-3">
<div class="file-section row mb-3"> <ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail>
<div class="col-3"> </div>
<ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail> <div class="col-7">
</div> <dl class="row">
<div class="col-7"> <dt class="col-md-4">
<dl class="row"> {{ "item.page.filesection.name" | translate }}
<dt class="col-md-4">{{"item.page.filesection.name" | translate}}</dt> </dt>
<dd class="col-md-8">{{ dsoNameService.getName(file) }}</dd> <dd class="col-md-8">{{ dsoNameService.getName(file) }}</dd>
<dt class="col-md-4">{{"item.page.filesection.size" | translate}}</dt> <dt class="col-md-4">
<dd class="col-md-8">{{(file.sizeBytes) | dsFileSize }}</dd> {{ "item.page.filesection.size" | translate }}
<dt class="col-md-4">{{"item.page.filesection.format" | translate}}</dt> </dt>
<dd class="col-md-8">{{(file.format | async)?.payload?.description}}</dd> <dd class="col-md-8">{{ file.sizeBytes | dsFileSize }}</dd>
@if (file.hasMetadata('dc.description')) { <dt class="col-md-4">
<dt class="col-md-4">{{"item.page.filesection.description" | translate}}</dt> {{ "item.page.filesection.format" | translate }}
<dd class="col-md-8">{{file.firstMetadataValue("dc.description")}}</dd> </dt>
} <dd class="col-md-8">
</dl> {{ (file.format | async)?.payload?.description }}
</div> </dd>
<div class="col-2"> @if (file.hasMetadata('dc.description')) {
<ds-file-download-link [bitstream]="file" [item]="item"> <dt class="col-md-4">
{{"item.page.filesection.download" | translate}} {{ "item.page.filesection.description" | translate }}
</ds-file-download-link> </dt>
</div> <dd class="col-md-8">
</div> {{ file.firstMetadataValue("dc.description") }}
} </dd>
</ds-pagination> }
</dl>
</div>
<div class="col-2">
<ds-file-download-link [showIcon]="true" [bitstream]="file" [item]="item" cssClasses="btn btn-outline-primary btn-download">
<span class="d-none d-md-inline">
{{ "item.page.filesection.download" | translate }}
</span>
</ds-file-download-link>
</div>
</div>
} }
</div> </ds-pagination>
}
</div>
} }
</div> </div>
<div *ngVar="(licenses$ | async)?.payload as licenses"> <div *ngVar="(licenses$ | async)?.payload as licenses">
@if (hasValuesInBundle(licenses)) { @if (hasValuesInBundle(licenses)) {
<div> <div>
<h3 class="h5 simple-view-element-header">{{"item.page.filesection.license.bundle" | translate}}</h3> <h3 class="h5 simple-view-element-header">
@if (licenses?.page?.length > 0) { {{ "item.page.filesection.license.bundle" | translate }}
<ds-pagination </h3>
[hideGear]="true" @if (licenses?.page?.length > 0) {
[hidePagerWhenSinglePage]="true" <ds-pagination [hideGear]="true" [hidePagerWhenSinglePage]="true" [paginationOptions]="licenseOptions"
[paginationOptions]="licenseOptions" [collectionSize]="licenses?.totalElements" [retainScrollPosition]="true">
[collectionSize]="licenses?.totalElements" @for (file of licenses?.page; track file) {
[retainScrollPosition]="true"> <div class="file-section row">
@for (file of licenses?.page; track file) { <div class="col-3">
<div class="file-section row"> <ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail>
<div class="col-3"> </div>
<ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail> <div class="col-7">
</div> <dl class="row">
<div class="col-7"> <dt class="col-md-4">
<dl class="row"> {{ "item.page.filesection.name" | translate }}
<dt class="col-md-4">{{"item.page.filesection.name" | translate}}</dt> </dt>
<dd class="col-md-8">{{ dsoNameService.getName(file) }}</dd> <dd class="col-md-8">{{ dsoNameService.getName(file) }}</dd>
<dt class="col-md-4">{{"item.page.filesection.size" | translate}}</dt> <dt class="col-md-4">
<dd class="col-md-8">{{(file.sizeBytes) | dsFileSize }}</dd> {{ "item.page.filesection.size" | translate }}
<dt class="col-md-4">{{"item.page.filesection.format" | translate}}</dt> </dt>
<dd class="col-md-8">{{(file.format | async)?.payload?.description}}</dd> <dd class="col-md-8">{{ file.sizeBytes | dsFileSize }}</dd>
<dt class="col-md-4">{{"item.page.filesection.description" | translate}}</dt> <dt class="col-md-4">
<dd class="col-md-8">{{file.firstMetadataValue("dc.description")}}</dd> {{ "item.page.filesection.format" | translate }}
</dl> </dt>
</div> <dd class="col-md-8">
<div class="col-2"> {{ (file.format | async)?.payload?.description }}
<ds-file-download-link [bitstream]="file" [item]="item"> </dd>
{{"item.page.filesection.download" | translate}} <dt class="col-md-4">
</ds-file-download-link> {{ "item.page.filesection.description" | translate }}
</div> </dt>
</div> <dd class="col-md-8">
} {{ file.firstMetadataValue("dc.description") }}
</ds-pagination> </dd>
</dl>
</div>
<div class="col-2">
<ds-file-download-link [showIcon]="true" [bitstream]="file" [item]="item" cssClasses="btn btn-outline-primary btn-download">
<span class="d-none d-md-inline">
{{ "item.page.filesection.download" | translate }}
</span>
</ds-file-download-link>
</div>
</div>
} }
</div> </ds-pagination>
}
</div>
} }
</div> </div>
</ds-metadata-field-wrapper> </ds-metadata-field-wrapper>

View File

@@ -5,17 +5,29 @@
[queryParams]="(bitstreamPath$| async)?.queryParams" [queryParams]="(bitstreamPath$| async)?.queryParams"
[target]="isBlank ? '_blank': '_self'" [target]="isBlank ? '_blank': '_self'"
[ngClass]="cssClasses" [ngClass]="cssClasses"
[attr.aria-label]="('file-download-link.download' | translate) + dsoNameService.getName(bitstream)" [attr.aria-label]="getDownloadLinkTitle(canDownload$ | async, canDownloadWithToken$ | async, dsoNameService.getName(bitstream))"
[title]="getDownloadLinkTitle(canDownload$ | async, canDownloadWithToken$ | async, dsoNameService.getName(bitstream))"
role="link" role="link"
tabindex="0"> tabindex="0">
@if ((canDownload$ | async) === false && (canDownloadWithToken$ | async) === false) { @if ((canDownload$ | async) === false && (canDownloadWithToken$ | async) === false) {
<!-- If the user cannot download the file by auth or token, show a lock icon --> <!-- If the user cannot download the file by auth or token, show a lock icon -->
<span role="img" [attr.aria-label]="'file-download-link.restricted' | translate" class="pr-1"><i class="fas fa-lock"></i></span> <span role="img"
[attr.aria-label]="'file-download-link.restricted' | translate"
[title]="'file-download-link.restricted' | translate"
class="pr-1">
<i class="fas fa-lock"></i>
</span>
} @else if ((canDownloadWithToken$ | async) && (canDownload$ | async) === false) { } @else if ((canDownloadWithToken$ | async) && (canDownload$ | async) === false) {
<!-- If the user can download the file by token, and NOT normally show a lock open icon --> <!-- If the user can download the file by token, and NOT normally show a lock open icon -->
<span role="img" [attr.aria-label]="'file-download-link.secure-access' | translate" class="pr-1 request-a-copy-access-icon"><i class="fa-solid fa-lock-open" style=""></i></span> <span role="img"
[attr.aria-label]="'file-download-link.secure-access' | translate"
[title]="'file-download-link.secure-access' | translate"
class="pr-1 request-a-copy-access-icon">
<i class="fa-solid fa-lock-open"></i>
</span>
} @else if (showIcon) {
<i class="fas fa-download d-inline"></i>
} }
<!-- Otherwise, show no icon (normal download by authorized user), public access etc. -->
<ng-container *ngTemplateOutlet="content"></ng-container> <ng-container *ngTemplateOutlet="content"></ng-container>
</a> </a>

View File

@@ -1,3 +1,7 @@
.request-a-copy-access-icon { .request-a-copy-access-icon {
color: var(--bs-success); color: var(--bs-success);
} }
.btn-download{
width: fit-content;
}

View File

@@ -12,7 +12,10 @@ import {
ActivatedRoute, ActivatedRoute,
RouterLink, RouterLink,
} from '@angular/router'; } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core'; import {
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import { import {
combineLatest as observableCombineLatest, combineLatest as observableCombineLatest,
Observable, Observable,
@@ -75,6 +78,11 @@ export class FileDownloadLinkComponent implements OnInit {
*/ */
@Input() showAccessStatusBadge = true; @Input() showAccessStatusBadge = true;
/**
* A boolean indicating whether the download icon should be displayed.
*/
@Input() showIcon = false;
itemRequest: ItemRequest; itemRequest: ItemRequest;
bitstreamPath$: Observable<{ bitstreamPath$: Observable<{
@@ -90,6 +98,7 @@ export class FileDownloadLinkComponent implements OnInit {
private authorizationService: AuthorizationDataService, private authorizationService: AuthorizationDataService,
public dsoNameService: DSONameService, public dsoNameService: DSONameService,
private route: ActivatedRoute, private route: ActivatedRoute,
private translateService: TranslateService,
) { ) {
} }
@@ -153,4 +162,9 @@ export class FileDownloadLinkComponent implements OnInit {
queryParams: {}, queryParams: {},
}; };
} }
getDownloadLinkTitle(canDownload: boolean,canDownloadWithToken: boolean, bitstreamName: string): string {
return (canDownload || canDownloadWithToken ? this.translateService.instant('file-download-link.download') :
this.translateService.instant('file-download-link.request-copy')) + bitstreamName;
}
} }

View File

@@ -29,6 +29,8 @@ export class ThemedFileDownloadLinkComponent extends ThemedComponent<FileDownloa
@Input() showAccessStatusBadge: boolean; @Input() showAccessStatusBadge: boolean;
@Input() showIcon = false;
protected inAndOutputNames: (keyof FileDownloadLinkComponent & keyof this)[] = [ protected inAndOutputNames: (keyof FileDownloadLinkComponent & keyof this)[] = [
'bitstream', 'bitstream',
'item', 'item',
@@ -36,6 +38,7 @@ export class ThemedFileDownloadLinkComponent extends ThemedComponent<FileDownloa
'isBlank', 'isBlank',
'enableRequestACopy', 'enableRequestACopy',
'showAccessStatusBadge', 'showAccessStatusBadge',
'showIcon',
]; ];
protected getComponentName(): string { protected getComponentName(): string {

View File

@@ -1,5 +1,5 @@
@if (showAccessStatus) { @if (showAccessStatus) {
@if ({ status: accessStatus$ | async, date: embargoDate$ | async }; as accessStatus) { @if ({ status: accessStatus$ | async, date: embargoDate$ | async }; as accessStatus) {
<span [class]="'badge bg-secondary access-status-list-element-badge ' + accessStatusClass">{{ accessStatus.status | translate: {date: accessStatus.date} }}</span> <span [class]="'badge bg-secondary dont-break-out access-status-list-element-badge ' + accessStatusClass">{{ accessStatus.status | translate: {date: accessStatus.date} }}</span>
} }
} }

View File

@@ -1,25 +1,30 @@
<div class="thumbnail" [class.limit-width]="limitWidth"> <div class="thumbnail" [class.limit-width]="limitWidth">
@if (isLoading) { @if (isLoading) {
<div class="thumbnail-content outer"> <div class="thumbnail-content outer">
<div class="inner"> <div class="inner">
<div class="centered"> <div class="centered">
<ds-loading [spinner]="true"></ds-loading> <ds-loading [spinner]="true"></ds-loading>
</div>
</div> </div>
</div> </div>
</div>
} }
<!-- don't use *ngIf="!isLoading" so the thumbnail can load in while the animation is playing --> <!-- don't use *ngIf="!isLoading" so the thumbnail can load in while the animation is playing -->
@if (src !== null) { @if (src !== null) {
<img class="thumbnail-content img-fluid" [ngClass]="{'d-none': isLoading}" <img
[src]="src | dsSafeUrl" [alt]="alt | translate" (error)="errorHandler()" (load)="successHandler()"> class="thumbnail-content img-fluid"
} [ngClass]="{ 'd-none': isLoading }"
@if (src === null && !isLoading) { [src]="src | dsSafeUrl"
<div class="thumbnail-content outer"> [alt]="alt | translate"
<div class="inner"> (error)="errorHandler()"
<div class="thumbnail-placeholder centered lead"> (load)="successHandler()"
{{ placeholder | translate }} />
</div> } @if (src === null && !isLoading) {
<div class="thumbnail-content outer">
<div class="inner">
<div class="thumbnail-placeholder centered">
{{ placeholder | translate }}
</div> </div>
</div> </div>
</div>
} }
</div> </div>

View File

@@ -7061,4 +7061,6 @@
"embargo.listelement.badge": "Embargo until {{ date }}", "embargo.listelement.badge": "Embargo until {{ date }}",
"metadata-export-search.submit.error.limit-exceeded": "Only the first {{limit}} items will be exported", "metadata-export-search.submit.error.limit-exceeded": "Only the first {{limit}} items will be exported",
"file-download-link.request-copy": "Request a copy of ",
} }