From d1bcb9fb2e79bc23b887332b1ac87dccd1fd06e0 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 11 Mar 2025 12:11:26 +0100 Subject: [PATCH] Request-a-copy: Use wrapped ItemWithSupp.. and base item comps, except download link --- src/app/core/auth/access-token.resolver.ts | 19 +- .../item-with-supplementary-data.model.ts | 20 ++ .../core/shared/media-viewer-item.model.ts | 5 + ...m-secure-file-download-link.component.html | 6 +- ...tem-secure-file-download-link.component.ts | 14 +- .../item-secure-file-section.component.html | 87 ----- .../item-secure-file-section.component.scss | 5 - ...item-secure-file-section.component.spec.ts | 96 ------ .../item-secure-file-section.component.ts | 156 --------- .../item-secure-media-viewer.component.html | 35 -- .../item-secure-media-viewer.component.scss | 1 - ...item-secure-media-viewer.component.spec.ts | 160 ---------- .../item-secure-media-viewer.component.ts | 171 ---------- .../secure-media-viewer-image.component.html | 7 - .../secure-media-viewer-image.component.scss | 20 -- ...ecure-media-viewer-image.component.spec.ts | 91 ------ .../secure-media-viewer-image.component.ts | 109 ------- .../secure-media-viewer-video.component.html | 54 ---- .../secure-media-viewer-video.component.scss | 10 - ...ecure-media-viewer-video.component.spec.ts | 149 --------- .../secure-media-viewer-video.component.ts | 103 ------ .../item-access-by-token-page.component.html | 8 - .../item-access-by-token-page.component.scss | 0 ...tem-access-by-token-page.component.spec.ts | 299 ------------------ .../item-access-by-token-page.component.ts | 178 ----------- .../item-access-by-token-view.component.html | 101 ------ .../item-access-by-token-view.component.scss | 0 ...tem-access-by-token-view.component.spec.ts | 213 ------------- .../item-access-by-token-view.component.ts | 128 -------- src/app/item-page/item-page-routes.ts | 3 +- .../media-viewer-image.component.ts | 3 +- .../media-viewer-video.component.html | 2 +- .../media-viewer-video.component.ts | 14 +- .../media-viewer/media-viewer.component.ts | 13 + .../file-section/file-section.component.ts | 7 + .../item-page/simple/item-page.component.ts | 50 ++- .../file-section/file-section.component.ts | 2 + .../register-email-form.component.ts | 1 + src/themes/custom/lazy-theme.module.ts | 4 - 39 files changed, 145 insertions(+), 2199 deletions(-) create mode 100644 src/app/core/shared/item-with-supplementary-data.model.ts delete mode 100644 src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.html delete mode 100644 src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.scss delete mode 100644 src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.spec.ts delete mode 100644 src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.ts delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.html delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.scss delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.spec.ts delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.ts delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.html delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.scss delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.spec.ts delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.ts delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.html delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.scss delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.spec.ts delete mode 100644 src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.ts delete mode 100644 src/app/item-page/access-by-token/item-access-by-token-page.component.html delete mode 100644 src/app/item-page/access-by-token/item-access-by-token-page.component.scss delete mode 100644 src/app/item-page/access-by-token/item-access-by-token-page.component.spec.ts delete mode 100644 src/app/item-page/access-by-token/item-access-by-token-page.component.ts delete mode 100644 src/app/item-page/access-by-token/item-access-by-token-view.component.html delete mode 100644 src/app/item-page/access-by-token/item-access-by-token-view.component.scss delete mode 100644 src/app/item-page/access-by-token/item-access-by-token-view.component.spec.ts delete mode 100644 src/app/item-page/access-by-token/item-access-by-token-view.component.ts diff --git a/src/app/core/auth/access-token.resolver.ts b/src/app/core/auth/access-token.resolver.ts index f29b047cfd..61deb89c4b 100644 --- a/src/app/core/auth/access-token.resolver.ts +++ b/src/app/core/auth/access-token.resolver.ts @@ -17,6 +17,15 @@ import { } from '../shared/operators'; import { AuthService } from './auth.service'; +/** + * Resolve an ItemRequest based on the accessToken in the query params + * Used in item-page-routes.ts to resolve the item request for all Item page components + * @param route + * @param state + * @param router + * @param authService + * @param itemRequestDataService + */ export const accessTokenResolver: ResolveFn = ( route, state, @@ -25,21 +34,23 @@ export const accessTokenResolver: ResolveFn = ( itemRequestDataService: ItemRequestDataService = inject(ItemRequestDataService), ): Observable => { const accessToken = route.queryParams.accessToken; + // Set null object if accesstoken is empty if ( !hasValue(accessToken) ) { return null; } - // Get + // Get the item request from the server return itemRequestDataService.getSanitizedRequestByAccessToken(accessToken).pipe( getFirstCompletedRemoteData(), + // Handle authorization errors, not found errors and forbidden errors as normal redirectOn4xx(router, authService), + // Get payload of the item request getFirstSucceededRemoteDataPayload(), tap(request => { if (!hasValue(request)) { - console.dir('no request found for access token', accessToken); + // If the request is not found, redirect to 403 Forbidden router.navigateByUrl(getForbiddenRoute()); } - console.dir('found request: ', request); - console.dir(request); + // Return the resolved item request object return request; }), ); diff --git a/src/app/core/shared/item-with-supplementary-data.model.ts b/src/app/core/shared/item-with-supplementary-data.model.ts new file mode 100644 index 0000000000..76480a2650 --- /dev/null +++ b/src/app/core/shared/item-with-supplementary-data.model.ts @@ -0,0 +1,20 @@ +import { Item } from './item.model'; +import { ItemRequest } from './item-request.model'; + +/** + * This model represents an item with supplementary data, e.g. an ItemRequest object + * to help components determine how the Item or its data/bitstream should be delivered + * and presented to the users, but not part of the actual database model. + */ +export class ItemWithSupplementaryData extends Item { + /** + * An item request. This is used to determine how the item should be delivered. + * A valid accessToken is resolved to this object in the accessTokenResolver + */ + itemRequest: ItemRequest; + + constructor(itemRequest: ItemRequest) { + super(); + this.itemRequest = itemRequest; + } +} diff --git a/src/app/core/shared/media-viewer-item.model.ts b/src/app/core/shared/media-viewer-item.model.ts index 1cf4948408..dd4fafeb34 100644 --- a/src/app/core/shared/media-viewer-item.model.ts +++ b/src/app/core/shared/media-viewer-item.model.ts @@ -23,4 +23,9 @@ export class MediaViewerItem { * Incoming Bitsream thumbnail */ thumbnail: string; + + /** + * Access token, if accessed via a Request-a-Copy link + */ + accessToken: string; } diff --git a/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.html b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.html index 476b3f356b..d38519878e 100644 --- a/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.html +++ b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.html @@ -1,4 +1,8 @@ - + diff --git a/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.ts b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.ts index 62b432e68a..4e60aaebe2 100644 --- a/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.ts +++ b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.ts @@ -23,11 +23,13 @@ import { getBitstreamDownloadWithAccessTokenRoute, getBitstreamRequestACopyRoute, } from '../../../../app-routing-paths'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../../core/data/feature-authorization/feature-id'; import { Bitstream } from '../../../../core/shared/bitstream.model'; import { Item } from '../../../../core/shared/item.model'; import { ItemRequest } from '../../../../core/shared/item-request.model'; +import { ItemWithSupplementaryData } from '../../../../core/shared/item-with-supplementary-data.model'; import { hasValue, isNotEmpty, @@ -54,6 +56,9 @@ export class ItemSecureFileDownloadLinkComponent implements OnInit { */ @Input() bitstream: Bitstream; + /** + * Item that owns the linked bitstream + */ @Input() item: Item; /** @@ -66,15 +71,14 @@ export class ItemSecureFileDownloadLinkComponent implements OnInit { */ @Input() isBlank = false; - @Input() itemRequest: ItemRequest; - @Input() enableRequestACopy = true; bitstreamPath$: Observable<{ routerLink: string, queryParams: any, }>; - + // ItemRequest object with access token, expiry, etc. + itemRequest: ItemRequest; // authorized to download normally canDownload$: Observable; // authorized to download with token @@ -84,6 +88,7 @@ export class ItemSecureFileDownloadLinkComponent implements OnInit { constructor( private authorizationService: AuthorizationDataService, + protected dsoNameService: DSONameService, ) { } @@ -92,6 +97,9 @@ export class ItemSecureFileDownloadLinkComponent implements OnInit { * (for a given bitstream), and ability to request a copy of a bitstream. */ ngOnInit() { + if (this.item instanceof ItemWithSupplementaryData) { + this.itemRequest = this.item.itemRequest; + } this.canDownload$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(this.bitstream) ? this.bitstream.self : undefined); this.canDownloadWithToken$ = observableOf(this.itemRequest ? (this.itemRequest.allfiles !== false || this.itemRequest.bitstreamId === this.bitstream.uuid) : false); this.canRequestACopy$ = this.authorizationService.isAuthorized(FeatureID.CanRequestACopy, isNotEmpty(this.bitstream) ? this.bitstream.self : undefined); diff --git a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.html b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.html deleted file mode 100644 index 4792f2657d..0000000000 --- a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.html +++ /dev/null @@ -1,87 +0,0 @@ - -
-
-

{{"item.page.filesection.original.bundle" | translate}}

- - - -
-
- -
-
-
-
{{"item.page.filesection.name" | translate}}
-
{{ dsoNameService.getName(file) }}
- -
{{"item.page.filesection.size" | translate}}
-
{{(file.sizeBytes) | dsFileSize }}
- - -
{{"item.page.filesection.format" | translate}}
-
{{(file.format | async)?.payload?.description}}
- - -
{{"item.page.filesection.description" | translate}}
-
{{file.firstMetadataValue("dc.description")}}
-
-
-
-
- - - - - {{"item.page.filesection.download" | translate}} - -
-
-
-
-
-
-
-

{{"item.page.filesection.license.bundle" | translate}}

- - - -
-
- -
-
-
-
{{"item.page.filesection.name" | translate}}
-
{{ dsoNameService.getName(file) }}
- -
{{"item.page.filesection.size" | translate}}
-
{{(file.sizeBytes) | dsFileSize }}
- -
{{"item.page.filesection.format" | translate}}
-
{{(file.format | async)?.payload?.description}}
- - -
{{"item.page.filesection.description" | translate}}
-
{{file.firstMetadataValue("dc.description")}}
-
-
-
- - {{"item.page.filesection.download" | translate}} - -
-
-
-
-
-
diff --git a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.scss b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.scss deleted file mode 100644 index 5384f90cec..0000000000 --- a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -@media screen and (min-width: map-get($grid-breakpoints, md)) { - dt { - text-align: right; - } -} diff --git a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.spec.ts b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.spec.ts deleted file mode 100644 index 557eb06b86..0000000000 --- a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { - ComponentFixture, - TestBed, - waitForAsync, -} from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { provideMockStore } from '@ngrx/store/testing'; -import { - TranslateLoader, - TranslateModule, -} from '@ngx-translate/core'; -import { of as observableOf } from 'rxjs'; -import { APP_CONFIG } from 'src/config/app-config.interface'; -import { environment } from 'src/environments/environment'; - -import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; -import { PaginationService } from '../../../../core/pagination/pagination.service'; -import { Bitstream } from '../../../../core/shared/bitstream.model'; -import { ThemedFileDownloadLinkComponent } from '../../../../shared/file-download-link/themed-file-download-link.component'; -import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; -import { MockBitstreamFormat1 } from '../../../../shared/mocks/item.mock'; -import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; -import { NotificationsService } from '../../../../shared/notifications/notifications.service'; -import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; -import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; -import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; -import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; -import { createPaginatedList } from '../../../../shared/testing/utils.test'; -import { FileSizePipe } from '../../../../shared/utils/file-size-pipe'; -import { VarDirective } from '../../../../shared/utils/var.directive'; -import { ThemedThumbnailComponent } from '../../../../thumbnail/themed-thumbnail.component'; -import { ItemSecureFileDownloadLinkComponent } from '../file-download-link/item-secure-file-download-link.component'; -import { ItemSecureFileSectionComponent } from './item-secure-file-section.component'; - -describe('FullFileSectionComponent', () => { - let comp: ItemSecureFileSectionComponent; - let fixture: ComponentFixture; - - const mockBitstream: Bitstream = Object.assign(new Bitstream(), { - sizeBytes: 10201, - content: 'test-content-url', - format: observableOf(MockBitstreamFormat1), - bundleName: 'ORIGINAL', - id: 'test-id', - _links: { - self: { href: 'test-href' }, - content: { href: 'test-content-href' }, - }, - }); - - const bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', { - findAllByItemAndBundleName: createSuccessfulRemoteDataObject$(createPaginatedList([mockBitstream, mockBitstream, mockBitstream])), - }); - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock, - }, - }), - BrowserAnimationsModule, - ItemSecureFileSectionComponent, - VarDirective, - FileSizePipe, - MetadataFieldWrapperComponent, - ], - providers: [ - provideMockStore(), - { provide: BitstreamDataService, useValue: bitstreamDataService }, - { provide: NotificationsService, useValue: new NotificationsServiceStub() }, - { provide: PaginationService, useValue: new PaginationServiceStub() }, - { provide: APP_CONFIG, useValue: environment }, - ], - schemas: [NO_ERRORS_SCHEMA], - }).overrideComponent(ItemSecureFileSectionComponent, { - remove: { imports: [PaginationComponent, MetadataFieldWrapperComponent,ItemSecureFileDownloadLinkComponent, ThemedThumbnailComponent, ThemedFileDownloadLinkComponent] }, - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ItemSecureFileSectionComponent); - comp = fixture.componentInstance; - fixture.detectChanges(); - }); - - describe('when the full file section gets loaded with bitstreams available', () => { - it('should contain a list with bitstreams', () => { - const fileSection = fixture.debugElement.queryAll(By.css('.file-section')); - expect(fileSection.length).toEqual(6); - }); - }); -}); diff --git a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.ts b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.ts deleted file mode 100644 index de1602a7f7..0000000000 --- a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { - Component, - Inject, - Input, - OnDestroy, - OnInit, -} from '@angular/core'; -import { - TranslateModule, - TranslateService, -} from '@ngx-translate/core'; -import { Observable } from 'rxjs'; -import { - switchMap, - tap, -} from 'rxjs/operators'; -import { - APP_CONFIG, - AppConfig, -} from 'src/config/app-config.interface'; - -import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; -import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; -import { PaginatedList } from '../../../../core/data/paginated-list.model'; -import { RemoteData } from '../../../../core/data/remote-data'; -import { PaginationService } from '../../../../core/pagination/pagination.service'; -import { Bitstream } from '../../../../core/shared/bitstream.model'; -import { Item } from '../../../../core/shared/item.model'; -import { ItemRequest } from '../../../../core/shared/item-request.model'; -import { - hasValue, - isEmpty, -} from '../../../../shared/empty.util'; -import { ThemedFileDownloadLinkComponent } from '../../../../shared/file-download-link/themed-file-download-link.component'; -import { ThemedLoadingComponent } from '../../../../shared/loading/themed-loading.component'; -import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; -import { NotificationsService } from '../../../../shared/notifications/notifications.service'; -import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; -import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; -import { FileSizePipe } from '../../../../shared/utils/file-size-pipe'; -import { followLink } from '../../../../shared/utils/follow-link-config.model'; -import { VarDirective } from '../../../../shared/utils/var.directive'; -import { ThemedThumbnailComponent } from '../../../../thumbnail/themed-thumbnail.component'; -import { FileSectionComponent } from '../../../simple/field-components/file-section/file-section.component'; -import { ItemSecureFileDownloadLinkComponent } from '../file-download-link/item-secure-file-download-link.component'; - -/** - * This component renders the file section of the item - * inside a 'ds-metadata-field-wrapper' component. - */ - -@Component({ - selector: 'ds-item-secure-full-file-section', - styleUrls: ['./item-secure-file-section.component.scss'], - templateUrl: './item-secure-file-section.component.html', - standalone: true, - imports: [ - ItemSecureFileDownloadLinkComponent, - CommonModule, - ThemedFileDownloadLinkComponent, - MetadataFieldWrapperComponent, - ThemedLoadingComponent, - TranslateModule, - FileSizePipe, - VarDirective, - PaginationComponent, - ThemedThumbnailComponent, - ], -}) -export class ItemSecureFileSectionComponent extends FileSectionComponent implements OnDestroy, OnInit { - - @Input() item: Item; - @Input() itemRequest: ItemRequest; - - label: string; - - originals$: Observable>>; - licenses$: Observable>>; - - originalOptions = Object.assign(new PaginationComponentOptions(), { - id: 'obo', - currentPage: 1, - pageSize: this.appConfig.item.bitstream.pageSize, - }); - - licenseOptions = Object.assign(new PaginationComponentOptions(), { - id: 'lbo', - currentPage: 1, - pageSize: this.appConfig.item.bitstream.pageSize, - }); - - constructor( - bitstreamDataService: BitstreamDataService, - protected notificationsService: NotificationsService, - protected translateService: TranslateService, - protected paginationService: PaginationService, - public dsoNameService: DSONameService, - @Inject(APP_CONFIG) protected appConfig: AppConfig, - ) { - super(bitstreamDataService, notificationsService, translateService, dsoNameService, appConfig); - } - - ngOnInit(): void { - this.initialize(); - } - - initialize(): void { - this.originals$ = this.paginationService.getCurrentPagination(this.originalOptions.id, this.originalOptions).pipe( - switchMap((options: PaginationComponentOptions) => this.bitstreamDataService.findAllByItemAndBundleName( - this.item, - 'ORIGINAL', - { elementsPerPage: options.pageSize, currentPage: options.currentPage }, - true, - true, - followLink('format'), - followLink('thumbnail'), - )), - tap((rd: RemoteData>) => { - if (hasValue(rd.errorMessage)) { - this.notificationsService.error(this.translateService.get('file-section.error.header'), `${rd.statusCode} ${rd.errorMessage}`); - } - }, - ), - ); - - this.licenses$ = this.paginationService.getCurrentPagination(this.licenseOptions.id, this.licenseOptions).pipe( - switchMap((options: PaginationComponentOptions) => this.bitstreamDataService.findAllByItemAndBundleName( - this.item, - 'LICENSE', - { elementsPerPage: options.pageSize, currentPage: options.currentPage }, - true, - true, - followLink('format'), - followLink('thumbnail'), - )), - tap((rd: RemoteData>) => { - if (hasValue(rd.errorMessage)) { - this.notificationsService.error(this.translateService.get('file-section.error.header'), `${rd.statusCode} ${rd.errorMessage}`); - } - }, - ), - ); - - } - - hasValuesInBundle(bundle: PaginatedList) { - return hasValue(bundle) && !isEmpty(bundle.page); - } - - ngOnDestroy(): void { - this.paginationService.clearPagination(this.originalOptions.id); - this.paginationService.clearPagination(this.licenseOptions.id); - } - -} diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.html b/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.html deleted file mode 100644 index 6acb4299ee..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.html +++ /dev/null @@ -1,35 +0,0 @@ - - -
- - - - - - - - - -
- - - - - -
diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.scss b/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.scss deleted file mode 100644 index 8b13789179..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.scss +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.spec.ts b/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.spec.ts deleted file mode 100644 index afc77ddb37..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.spec.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { - ComponentFixture, - TestBed, - waitForAsync, -} from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { - TranslateLoader, - TranslateModule, -} from '@ngx-translate/core'; -import { of as observableOf } from 'rxjs'; - -import { AuthService } from '../../../../core/auth/auth.service'; -import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; -import { Bitstream } from '../../../../core/shared/bitstream.model'; -import { MediaViewerItem } from '../../../../core/shared/media-viewer-item.model'; -import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; -import { AuthServiceMock } from '../../../../shared/mocks/auth.service.mock'; -import { MockBitstreamFormat1 } from '../../../../shared/mocks/item.mock'; -import { getMockThemeService } from '../../../../shared/mocks/theme-service.mock'; -import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; -import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; -import { createPaginatedList } from '../../../../shared/testing/utils.test'; -import { ThemeService } from '../../../../shared/theme-support/theme.service'; -import { FileSizePipe } from '../../../../shared/utils/file-size-pipe'; -import { VarDirective } from '../../../../shared/utils/var.directive'; -import { ItemSecureMediaViewerComponent } from './item-secure-media-viewer.component'; - -describe('ItemSecureMediaViewerComponent', () => { - let comp: ItemSecureMediaViewerComponent; - let fixture: ComponentFixture; - - const mockBitstream: Bitstream = Object.assign(new Bitstream(), { - sizeBytes: 10201, - content: - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content', - format: observableOf(MockBitstreamFormat1), - bundleName: 'ORIGINAL', - _links: { - self: { - href: - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713', - }, - content: { - href: - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content', - }, - }, - id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', - uuid: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', - type: 'bitstream', - metadata: { - 'dc.title': [ - { - language: null, - value: 'test_word.docx', - }, - ], - }, - }); - - const bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', { - findAllByItemAndBundleName: createSuccessfulRemoteDataObject$( - createPaginatedList([mockBitstream]), - ), - }); - - const mockMediaViewerItem: MediaViewerItem = Object.assign( - new MediaViewerItem(), - { bitstream: mockBitstream, format: 'image', thumbnail: null }, - ); - - beforeEach(waitForAsync(() => { - return TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock, - }, - }), - BrowserAnimationsModule, - ItemSecureMediaViewerComponent, - VarDirective, - FileSizePipe, - MetadataFieldWrapperComponent, - ], - providers: [ - { provide: BitstreamDataService, useValue: bitstreamDataService }, - { provide: ThemeService, useValue: getMockThemeService() }, - { provide: AuthService, useValue: new AuthServiceMock() }, - ], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ItemSecureMediaViewerComponent); - comp = fixture.componentInstance; - fixture.detectChanges(); - }); - - describe('when the bitstreams are loading', () => { - beforeEach(() => { - comp.mediaList$.next([mockMediaViewerItem]); - comp.mediaOptions = { - image: true, - video: true, - }; - comp.isLoading = true; - fixture.detectChanges(); - }); - - it('should call the createMediaViewerItem', () => { - const mediaItem = comp.createMediaViewerItem( - mockBitstream, - MockBitstreamFormat1, - undefined, - ); - expect(mediaItem).toBeTruthy(); - expect(mediaItem.thumbnail).toBe(null); - }); - - it('should display a loading component', () => { - const loading = fixture.debugElement.query(By.css('ds-loading')); - expect(loading.nativeElement).toBeDefined(); - }); - }); - - describe('when the bitstreams loading is failed', () => { - beforeEach(() => { - comp.mediaList$.next([]); - comp.mediaOptions = { - image: true, - video: true, - }; - comp.isLoading = false; - fixture.detectChanges(); - }); - - it('should call the createMediaViewerItem', () => { - const mediaItem = comp.createMediaViewerItem( - mockBitstream, - MockBitstreamFormat1, - undefined, - ); - expect(mediaItem).toBeTruthy(); - expect(mediaItem.thumbnail).toBe(null); - }); - - it('should display a default, thumbnail', () => { - const defaultThumbnail = fixture.debugElement.query( - By.css('ds-secure-media-viewer-image'), - ); - expect(defaultThumbnail.nativeElement).toBeDefined(); - }); - }); -}); diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.ts b/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.ts deleted file mode 100644 index fb06d0ec95..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/item-secure-media-viewer.component.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { - AsyncPipe, - NgIf, -} from '@angular/common'; -import { - ChangeDetectorRef, - Component, - Input, - OnDestroy, - OnInit, -} from '@angular/core'; -import { TranslateModule } from '@ngx-translate/core'; -import { - BehaviorSubject, - Observable, - Subscription, -} from 'rxjs'; -import { - filter, - take, -} from 'rxjs/operators'; - -import { MediaViewerConfig } from '../../../../../config/media-viewer-config.interface'; -import { environment } from '../../../../../environments/environment'; -import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; -import { PaginatedList } from '../../../../core/data/paginated-list.model'; -import { RemoteData } from '../../../../core/data/remote-data'; -import { Bitstream } from '../../../../core/shared/bitstream.model'; -import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; -import { Item } from '../../../../core/shared/item.model'; -import { MediaViewerItem } from '../../../../core/shared/media-viewer-item.model'; -import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; -import { hasValue } from '../../../../shared/empty.util'; -import { ThemedLoadingComponent } from '../../../../shared/loading/themed-loading.component'; -import { followLink } from '../../../../shared/utils/follow-link-config.model'; -import { VarDirective } from '../../../../shared/utils/var.directive'; -import { ThemedThumbnailComponent } from '../../../../thumbnail/themed-thumbnail.component'; -import { SecureMediaViewerImageComponent } from './media-viewer-image/secure-media-viewer-image.component'; -import { SecureMediaViewerVideoComponent } from './media-viewer-video/secure-media-viewer-video.component'; - -/** - * This component renders the media viewers - */ -@Component({ - selector: 'ds-item-secure-media-viewer', - templateUrl: './item-secure-media-viewer.component.html', - styleUrls: ['./item-secure-media-viewer.component.scss'], - imports: [ - AsyncPipe, - NgIf, - TranslateModule, - ThemedLoadingComponent, - VarDirective, - SecureMediaViewerVideoComponent, - SecureMediaViewerImageComponent, - ThemedThumbnailComponent, - ], - standalone: true, -}) -export class ItemSecureMediaViewerComponent implements OnDestroy, OnInit { - @Input() item: Item; - - @Input() mediaOptions: MediaViewerConfig = environment.mediaViewer; - - @Input() accessToken: string; - - mediaList$: BehaviorSubject = new BehaviorSubject([]); - - captions$: BehaviorSubject = new BehaviorSubject([]); - - isLoading = true; - - thumbnailPlaceholder = './assets/images/replacement_document.svg'; - - thumbnailsRD$: Observable>>; - - subs: Subscription[] = []; - - constructor( - protected bitstreamDataService: BitstreamDataService, - protected changeDetectorRef: ChangeDetectorRef, - ) { - } - - ngOnDestroy(): void { - this.subs.forEach((subscription: Subscription) => subscription.unsubscribe()); - } - - /** - * This method loads all the Bitstreams and Thumbnails and converts it to {@link MediaViewerItem}s - */ - ngOnInit(): void { - const types: string[] = [ - ...(this.mediaOptions.image ? ['image'] : []), - ...(this.mediaOptions.video ? ['audio', 'video'] : []), - ]; - this.thumbnailsRD$ = this.loadRemoteData('THUMBNAIL'); - this.subs.push(this.loadRemoteData('ORIGINAL').subscribe((bitstreamsRD: RemoteData>) => { - if (bitstreamsRD.payload.page.length === 0) { - this.isLoading = false; - this.mediaList$.next([]); - } else { - this.subs.push(this.thumbnailsRD$.subscribe((thumbnailsRD: RemoteData>) => { - for ( - let index = 0; - index < bitstreamsRD.payload.page.length; - index++ - ) { - this.subs.push(bitstreamsRD.payload.page[index].format - .pipe(getFirstSucceededRemoteDataPayload()) - .subscribe((format: BitstreamFormat) => { - const mediaItem = this.createMediaViewerItem( - bitstreamsRD.payload.page[index], - format, - thumbnailsRD.payload && thumbnailsRD.payload.page[index], - ); - if (types.includes(mediaItem.format)) { - this.mediaList$.next([...this.mediaList$.getValue(), mediaItem]); - } else if (format.mimetype === 'text/vtt') { - this.captions$.next([...this.captions$.getValue(), bitstreamsRD.payload.page[index]]); - } - })); - } - this.isLoading = false; - this.changeDetectorRef.detectChanges(); - })); - } - })); - } - - /** - * This method will retrieve the next page of Bitstreams from the external BitstreamDataService call. - * @param bundleName Bundle name - */ - loadRemoteData( - bundleName: string, - ): Observable>> { - return this.bitstreamDataService - .findAllByItemAndBundleName( - this.item, - bundleName, - {}, - true, - true, - followLink('format'), - ) - .pipe( - filter( - (bitstreamsRD: RemoteData>) => - hasValue(bitstreamsRD) && - (hasValue(bitstreamsRD.errorMessage) || hasValue(bitstreamsRD.payload)), - ), - take(1), - ); - } - - /** - * This method creates a {@link MediaViewerItem} from incoming {@link Bitstream}s - * @param original original bitstream - * @param format original bitstream format - * @param thumbnail thumbnail bitstream - */ - createMediaViewerItem(original: Bitstream, format: BitstreamFormat, thumbnail: Bitstream): MediaViewerItem { - const mediaItem = new MediaViewerItem(); - mediaItem.bitstream = original; - mediaItem.format = format.mimetype.split('/')[0]; - mediaItem.mimetype = format.mimetype; - mediaItem.thumbnail = thumbnail ? thumbnail._links.content.href : null; - return mediaItem; - } -} diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.html b/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.html deleted file mode 100644 index bafc6f079c..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
- -
diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.scss b/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.scss deleted file mode 100644 index cba963b6fa..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -:host ::ng-deep { - .ngx-gallery { - width: unset !important; - height: unset !important; - } - - ngx-gallery-image { - max-width: 340px !important; - - .ngx-gallery-image { - background-position: left; - } - } - - ngx-gallery-image:after { - padding-top: 75%; - display: block; - content: ''; - } -} diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.spec.ts b/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.spec.ts deleted file mode 100644 index 536c073e41..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { - ComponentFixture, - TestBed, - waitForAsync, -} from '@angular/core/testing'; -import { NgxGalleryOptions } from '@kolkov/ngx-gallery'; -import { of as observableOf } from 'rxjs'; - -import { AuthService } from '../../../../../core/auth/auth.service'; -import { Bitstream } from '../../../../../core/shared/bitstream.model'; -import { MediaViewerItem } from '../../../../../core/shared/media-viewer-item.model'; -import { MockBitstreamFormat1 } from '../../../../../shared/mocks/item.mock'; -import { SecureMediaViewerImageComponent } from './secure-media-viewer-image.component'; - -describe('ItemSecureMediaViewerImageComponent', () => { - let component: SecureMediaViewerImageComponent; - let fixture: ComponentFixture; - - const authService = jasmine.createSpyObj('authService', { - isAuthenticated: observableOf(false), - }); - - const mockBitstream: Bitstream = Object.assign(new Bitstream(), { - sizeBytes: 10201, - content: - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content', - format: observableOf(MockBitstreamFormat1), - bundleName: 'ORIGINAL', - _links: { - self: { - href: - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713', - }, - content: { - href: - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content', - }, - }, - id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', - uuid: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', - type: 'bitstream', - metadata: { - 'dc.title': [ - { - language: null, - value: 'test_word.docx', - }, - ], - }, - }); - - const mockMediaViewerItems: MediaViewerItem[] = Object.assign( - new Array(), - [ - { bitstream: mockBitstream, format: 'image', thumbnail: null }, - { bitstream: mockBitstream, format: 'image', thumbnail: null }, - ], - ); - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [SecureMediaViewerImageComponent], - schemas: [NO_ERRORS_SCHEMA], - providers: [ - { provide: AuthService, useValue: authService }, - ], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SecureMediaViewerImageComponent); - component = fixture.componentInstance; - component.galleryOptions = [new NgxGalleryOptions({})]; - component.galleryImages = component.convertToGalleryImage( - mockMediaViewerItems, - ); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should contain a gallery options', () => { - expect(component.galleryOptions.length).toBeGreaterThan(0); - }); - - it('should contain an image array', () => { - expect(component.galleryImages.length).toBeGreaterThan(0); - }); -}); diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.ts b/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.ts deleted file mode 100644 index c6c9cec24a..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-image/secure-media-viewer-image.component.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { AsyncPipe } from '@angular/common'; -import { - Component, - Input, - OnChanges, - OnInit, -} from '@angular/core'; -import { - NgxGalleryAnimation, - NgxGalleryImage, - NgxGalleryModule, - NgxGalleryOptions, -} from '@kolkov/ngx-gallery'; -import { Observable } from 'rxjs'; - -import { AuthService } from '../../../../../core/auth/auth.service'; -import { MediaViewerItem } from '../../../../../core/shared/media-viewer-item.model'; - -/** - * This componenet render an image gallery for the image viewer - */ -@Component({ - selector: 'ds-secure-media-viewer-image', - templateUrl: './secure-media-viewer-image.component.html', - styleUrls: ['./secure-media-viewer-image.component.scss'], - imports: [ - NgxGalleryModule, - AsyncPipe, - ], - standalone: true, -}) -export class SecureMediaViewerImageComponent implements OnChanges, OnInit { - @Input() images: MediaViewerItem[]; - @Input() preview?: boolean; - @Input() image?: string; - @Input() accessToken: string; - - thumbnailPlaceholder = './assets/images/replacement_image.svg'; - - galleryOptions: NgxGalleryOptions[] = []; - - galleryImages: NgxGalleryImage[] = []; - - /** - * Whether or not the current user is authenticated - */ - isAuthenticated$: Observable; - - constructor( - protected authService: AuthService, - ) { - } - - ngOnChanges(): void { - this.galleryOptions = [ - { - preview: this.preview !== undefined ? this.preview : true, - image: true, - imageSize: 'contain', - thumbnails: false, - imageArrows: false, - startIndex: 0, - imageAnimation: NgxGalleryAnimation.Slide, - previewCloseOnEsc: true, - previewZoom: true, - previewRotate: true, - previewFullscreen: true, - }, - ]; - if (this.image) { - this.galleryImages = [ - { - small: this.image, - medium: this.image, - big: this.image, - }, - ]; - } else { - this.galleryImages = this.convertToGalleryImage(this.images); - } - } - - ngOnInit(): void { - this.isAuthenticated$ = this.authService.isAuthenticated(); - this.ngOnChanges(); - } - - /** - * This method convert an array of MediaViewerItem into NgxGalleryImage array - * @param medias input NgxGalleryImage array - */ - convertToGalleryImage(medias: MediaViewerItem[]): NgxGalleryImage[] { - const mappedImages = []; - for (const image of medias) { - if (image.format === 'image') { - mappedImages.push({ - small: image.thumbnail - ? image.thumbnail - : this.thumbnailPlaceholder, - medium: image.thumbnail - ? image.thumbnail - : this.thumbnailPlaceholder, - big: image.bitstream._links.content.href + '?accessToken=' + this.accessToken, - }); - } - } - return mappedImages; - } -} diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.html b/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.html deleted file mode 100644 index 8a6a447d9e..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.html +++ /dev/null @@ -1,54 +0,0 @@ - -
- - - -
- -
- -
-
-
diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.scss b/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.scss deleted file mode 100644 index bb8b9d360e..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -video { - width: 100%; - height: auto; - max-width: 340px; -} - -.buttons { - display: flex; - gap: .25rem; -} diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.spec.ts b/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.spec.ts deleted file mode 100644 index 1e3282641c..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.spec.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { - ComponentFixture, - TestBed, - waitForAsync, -} from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { - TranslateLoader, - TranslateModule, -} from '@ngx-translate/core'; -import { of as observableOf } from 'rxjs'; - -import { Bitstream } from '../../../../../core/shared/bitstream.model'; -import { MediaViewerItem } from '../../../../../core/shared/media-viewer-item.model'; -import { MetadataFieldWrapperComponent } from '../../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; -import { MockBitstreamFormat1 } from '../../../../../shared/mocks/item.mock'; -import { TranslateLoaderMock } from '../../../../../shared/mocks/translate-loader.mock'; -import { FileSizePipe } from '../../../../../shared/utils/file-size-pipe'; -import { VarDirective } from '../../../../../shared/utils/var.directive'; -import { SecureMediaViewerVideoComponent } from './secure-media-viewer-video.component'; - -describe('SecureMediaViewerVideoComponent', () => { - let component: SecureMediaViewerVideoComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock, - }, - }), - BrowserAnimationsModule, - SecureMediaViewerVideoComponent, - VarDirective, - FileSizePipe, - MetadataFieldWrapperComponent, - ], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - })); - - const mockBitstream: Bitstream = Object.assign(new Bitstream(), { - sizeBytes: 10201, - content: - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content', - format: observableOf(MockBitstreamFormat1), - bundleName: 'ORIGINAL', - _links: { - self: { - href: - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713', - }, - content: { - href: - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content', - }, - }, - id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', - uuid: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', - type: 'bitstream', - metadata: { - 'dc.title': [ - { - language: null, - value: 'test_word.docx', - }, - ], - }, - }); - - const mockMediaViewerItems: MediaViewerItem[] = Object.assign( - new Array(), - [ - { bitstream: mockBitstream, format: 'video', thumbnail: null }, - { bitstream: mockBitstream, format: 'video', thumbnail: null }, - ], - ); - const mockMediaViewerItem: MediaViewerItem[] = Object.assign( - new Array(), - [{ bitstream: mockBitstream, format: 'video', thumbnail: null }], - ); - - beforeEach(() => { - fixture = TestBed.createComponent(SecureMediaViewerVideoComponent); - component = fixture.componentInstance; - component.medias = mockMediaViewerItem; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('should show controller buttons when the having mode then one video', () => { - beforeEach(() => { - component.medias = mockMediaViewerItems; - fixture.detectChanges(); - }); - - it('should show buttons', () => { - const controllerButtons = fixture.debugElement.query(By.css('.buttons')); - expect(controllerButtons).toBeTruthy(); - }); - - describe('when the "Next" button is clicked', () => { - beforeEach(() => { - component.currentIndex = 0; - fixture.detectChanges(); - }); - - it('should increase the index', () => { - const viewMore = fixture.debugElement.query(By.css('.next')); - viewMore.triggerEventHandler('click', null); - expect(component.currentIndex).toBe(1); - }); - }); - - describe('when the "Previous" button is clicked', () => { - beforeEach(() => { - component.currentIndex = 1; - fixture.detectChanges(); - }); - - it('should decrease the index', () => { - const viewMore = fixture.debugElement.query(By.css('.previous')); - viewMore.triggerEventHandler('click', null); - expect(component.currentIndex).toBe(0); - }); - }); - - describe('when the "Playlist element" button is clicked', () => { - beforeEach(() => { - component.isCollapsed = true; - fixture.detectChanges(); - }); - - it('should set the the index with the selected one', () => { - const viewMore = fixture.debugElement.query(By.css('.list-element')); - viewMore.triggerEventHandler('click', null); - expect(component.currentIndex).toBe(0); - }); - }); - }); -}); diff --git a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.ts b/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.ts deleted file mode 100644 index 75f597c8a2..0000000000 --- a/src/app/item-page/access-by-token/field-components/media-viewer/media-viewer-video/secure-media-viewer-video.component.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { - NgForOf, - NgIf, -} from '@angular/common'; -import { - Component, - Input, -} from '@angular/core'; -import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateModule } from '@ngx-translate/core'; -import { Bitstream } from 'src/app/core/shared/bitstream.model'; - -import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; -import { MediaViewerItem } from '../../../../../core/shared/media-viewer-item.model'; -import { BtnDisabledDirective } from '../../../../../shared/btn-disabled.directive'; -import { CaptionInfo } from '../../../../media-viewer/media-viewer-video/caption-info'; -import { languageHelper } from '../../../../media-viewer/media-viewer-video/language-helper'; - -/** - * This component renders a video viewer and playlist for the media viewer - */ -@Component({ - selector: 'ds-secure-media-viewer-video', - templateUrl: './secure-media-viewer-video.component.html', - styleUrls: ['./secure-media-viewer-video.component.scss'], - imports: [ - NgForOf, - NgbDropdownModule, - TranslateModule, - NgIf, - BtnDisabledDirective, - ], - standalone: true, -}) -export class SecureMediaViewerVideoComponent { - @Input() medias: MediaViewerItem[]; - - @Input() captions: Bitstream[] = []; - - @Input() accessToken: string; - - isCollapsed = false; - - currentIndex = 0; - - replacements = { - video: './assets/images/replacement_video.svg', - audio: './assets/images/replacement_audio.svg', - }; - - constructor( - public dsoNameService: DSONameService, - ) { - } - - /** - * This method check if there is caption file for the media - * The caption file name is the media name plus "-" following two letter - * language code and .vtt suffix - * - * html5 video only support WEBVTT format - * - * Two letter language code reference - * https://www.w3schools.com/tags/ref_language_codes.asp - */ - getMediaCap(name: string, captions: Bitstream[]): CaptionInfo[] { - const capInfos: CaptionInfo[] = []; - const filteredCapMedias: Bitstream[] = captions - .filter((media: Bitstream) => media.name.substring(0, (media.name.length - 7)).toLowerCase() === name.toLowerCase()); - - for (const media of filteredCapMedias) { - const srclang: string = media.name.slice(-6, -4).toLowerCase(); - capInfos.push(new CaptionInfo( - media._links.content.href + '?accessToken=' + this.accessToken, - srclang, - languageHelper[srclang], - )); - } - return capInfos; - } - - /** - * This method sets the received index into currentIndex - * @param index Selected index - */ - selectedMedia(index: number) { - this.currentIndex = index; - } - - /** - * This method increases the number of the currentIndex - */ - nextMedia() { - this.currentIndex++; - } - - /** - * This method decreases the number of the currentIndex - */ - prevMedia() { - this.currentIndex--; - } -} diff --git a/src/app/item-page/access-by-token/item-access-by-token-page.component.html b/src/app/item-page/access-by-token/item-access-by-token-page.component.html deleted file mode 100644 index a99f01858b..0000000000 --- a/src/app/item-page/access-by-token/item-access-by-token-page.component.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
- - -
-
diff --git a/src/app/item-page/access-by-token/item-access-by-token-page.component.scss b/src/app/item-page/access-by-token/item-access-by-token-page.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/item-page/access-by-token/item-access-by-token-page.component.spec.ts b/src/app/item-page/access-by-token/item-access-by-token-page.component.spec.ts deleted file mode 100644 index e270b34669..0000000000 --- a/src/app/item-page/access-by-token/item-access-by-token-page.component.spec.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { KeyValuePipe } from '@angular/common'; -import { PLATFORM_ID } from '@angular/core'; -import { - ComponentFixture, - fakeAsync, - TestBed, -} from '@angular/core/testing'; -import { - ActivatedRoute, - Router, -} from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { Store } from '@ngrx/store'; -import { TranslateModule } from '@ngx-translate/core'; -import { - BehaviorSubject, - of as observableOf, -} from 'rxjs'; - -import { getForbiddenRoute } from '../../app-routing-paths'; -import { AuthService } from '../../core/auth/auth.service'; -import { NotifyInfoService } from '../../core/coar-notify/notify-info/notify-info.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { ItemDataService } from '../../core/data/item-data.service'; -import { ItemRequestDataService } from '../../core/data/item-request-data.service'; -import { SignpostingDataService } from '../../core/data/signposting-data.service'; -import { LinkHeadService } from '../../core/services/link-head.service'; -import { ServerResponseService } from '../../core/services/server-response.service'; -import { Item } from '../../core/shared/item.model'; -import { ItemRequest } from '../../core/shared/item-request.model'; -import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; -import { ItemAccessByTokenPageComponent } from './item-access-by-token-page.component'; -import { ItemAccessByTokenViewComponent } from './item-access-by-token-view.component'; - -describe('ItemAccessByTokenPageComponent', () => { - let component: ItemAccessByTokenPageComponent; - let fixture: ComponentFixture; - let itemRequestService: jasmine.SpyObj; - let router: jasmine.SpyObj; - let authorizationService: AuthorizationDataService; - authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(false), - }); - let signpostingDataService: SignpostingDataService; - - const mocklink = { - href: 'http://test.org', - rel: 'test', - type: 'test', - }; - - const mocklink2 = { - href: 'http://test2.org', - rel: 'test', - type: 'test', - }; - signpostingDataService = jasmine.createSpyObj('SignpostingDataService', { - getLinks: observableOf([mocklink, mocklink2]), - }); - const linkHeadService = jasmine.createSpyObj('linkHeadService', { - addTag: '', - }); - - const mockItem = Object.assign(new Item(), { - uuid: 'test-item-uuid', - id: 'test-item-id', - metadata: { - 'dspace.entity.type': [{ - value: 'Publication', - language: 'en', - place: 0, - authority: null, - confidence: -1, - }], - }, - _links: { - self: { href: 'obj-selflink' }, - }, - }); - - const mockItemRequest = Object.assign(new ItemRequest(), { - token: 'valid-token', - accessToken: 'valid-token', - itemId: mockItem.uuid, - }); - - const queryParams = { accessToken: 'valid-token' }; - const mockActivatedRoute = { - queryParams: new BehaviorSubject(queryParams), - data: observableOf({ - dso: createSuccessfulRemoteDataObject(mockItem), - }), - params: observableOf({ itemId: mockItem.uuid, queryParams: [ { accessToken: 'valid-token' } ] }), - children: [], - }; - itemRequestService = jasmine.createSpyObj('ItemRequestDataService', { - getSanitizedRequestByAccessToken: observableOf(createSuccessfulRemoteDataObject(mockItemRequest)), - }); - router = jasmine.createSpyObj('Router', ['navigateByUrl'], { - events: observableOf([]), - }); - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), - KeyValuePipe, - ], - providers: [ - { provide: ItemRequestDataService, useValue: itemRequestService }, - { provide: Router, useValue: router }, - { provide: ActivatedRoute, useValue: mockActivatedRoute }, - { provide: ItemDataService, useValue: {} }, - { provide: AuthorizationDataService, useValue: authorizationService }, - { provide: ServerResponseService, useValue: {} }, - { provide: SignpostingDataService, useValue: signpostingDataService }, - { provide: LinkHeadService, useValue: linkHeadService }, - { provide: NotifyInfoService, useValue: { isCoarConfigEnabled: () => observableOf(false) } }, - { provide: PLATFORM_ID, useValue: 'browser' }, - KeyValuePipe, - { - provide: Store, - useValue: { - pipe: () => observableOf({}), - dispatch: () => { - }, - select: () => observableOf({}), - }, - }, - { - provide: AuthService, useValue: { - isAuthenticated: () => observableOf(true), - }, - }, - ], - }).overrideComponent(ItemAccessByTokenPageComponent, { - set: { - template: '
', - }, - }).overrideComponent(ItemAccessByTokenViewComponent, { - set: { - template: '
', - }, - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ItemAccessByTokenPageComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - - }); - - /** - * Tests in this component are concerned only with successful access token processing (or error handling) - * and a resulting item request object. Testing of template elements is out of scope and left for child components. - */ - describe('ngOnInit - basic component testing', () => { - it('should find valid access token and sanitize it', fakeAsync(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), - KeyValuePipe, - RouterTestingModule.withRoutes([]), - ], - providers: [ - { provide: ItemRequestDataService, useValue: itemRequestService }, - { provide: Router, useValue: router }, - { provide: ActivatedRoute, useValue: mockActivatedRoute }, - { provide: AuthService, useValue: {} }, - { provide: ItemDataService, useValue: {} }, - { provide: AuthorizationDataService, useValue: authorizationService }, - { provide: ServerResponseService, useValue: {} }, - { provide: SignpostingDataService, useValue: signpostingDataService }, - { provide: LinkHeadService, useValue: linkHeadService }, - { provide: NotifyInfoService, useValue: { isCoarConfigEnabled: () => observableOf(false) } }, - { provide: PLATFORM_ID, useValue: 'browser' }, - KeyValuePipe, - { - provide: Store, - useValue: { - pipe: () => observableOf({}), - dispatch: () => {}, - select: () => observableOf({}), - }, - }, - { provide: AuthService, useValue: { - isAuthenticated: () => observableOf(false ) }, - }, - ], - }).overrideComponent(ItemAccessByTokenViewComponent, { - set: { template: '
' } } ).compileComponents(); - - fixture = TestBed.createComponent(ItemAccessByTokenPageComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - expect(itemRequestService.getSanitizedRequestByAccessToken).toHaveBeenCalledWith('valid-token'); - - })); - - it('should process valid access token and load item request', fakeAsync(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), - KeyValuePipe, - RouterTestingModule.withRoutes([]), - ], - providers: [ - { provide: ItemRequestDataService, useValue: itemRequestService }, - { provide: Router, useValue: router }, - { provide: ActivatedRoute, useValue: mockActivatedRoute }, - { provide: AuthService, useValue: {} }, - { provide: ItemDataService, useValue: {} }, - { provide: AuthorizationDataService, useValue: authorizationService }, - { provide: ServerResponseService, useValue: {} }, - { provide: SignpostingDataService, useValue: signpostingDataService }, - { provide: LinkHeadService, useValue: linkHeadService }, - { provide: NotifyInfoService, useValue: { isCoarConfigEnabled: () => observableOf(false) } }, - { provide: PLATFORM_ID, useValue: 'browser' }, - KeyValuePipe, - { - provide: Store, - useValue: { - pipe: () => observableOf({}), - dispatch: () => {}, - select: () => observableOf({}), - }, - }, - { provide: AuthService, useValue: { - isAuthenticated: () => observableOf(false ) }, - }, - ], - }).overrideComponent(ItemAccessByTokenViewComponent, { - set: { template: '
' } } ).compileComponents(); - - fixture = TestBed.createComponent(ItemAccessByTokenPageComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - - component.itemRequest$.subscribe((request) => { - expect(request).toBeTruthy(); - }); - })); - - it('should redirect to forbidden route when access token is missing', fakeAsync(() => { - const routeWithoutToken = { - queryParams: observableOf({}), - data: observableOf({ - dso: createSuccessfulRemoteDataObject(mockItem), - }), - params: observableOf({ itemId: mockItem.uuid }), - children: [], - }; - - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), - KeyValuePipe, - ], - providers: [ - { provide: ItemRequestDataService, useValue: itemRequestService }, - { provide: Router, useValue: router }, - { provide: ActivatedRoute, useValue: routeWithoutToken }, - { provide: AuthService, useValue: {} }, - { provide: ItemDataService, useValue: {} }, - { provide: AuthorizationDataService, useValue: authorizationService }, - { provide: ServerResponseService, useValue: {} }, - { provide: SignpostingDataService, useValue: signpostingDataService }, - { provide: LinkHeadService, useValue: linkHeadService }, - { provide: NotifyInfoService, useValue: { isCoarConfigEnabled: () => observableOf(false) } }, - { provide: PLATFORM_ID, useValue: 'browser' }, - { - provide: Store, - useValue: { - pipe: () => observableOf({}), - dispatch: () => {}, - select: () => observableOf({}), - }, - }, - { provide: AuthService, useValue: { - isAuthenticated: () => observableOf(false ) }, - }, - ], - }).overrideComponent(ItemAccessByTokenViewComponent, { - set: { - template: '
', - } }) - .compileComponents(); - - fixture = TestBed.createComponent(ItemAccessByTokenPageComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - expect(router.navigateByUrl).toHaveBeenCalledWith(getForbiddenRoute(), { skipLocationChange: false }); - })); - }); -}); - diff --git a/src/app/item-page/access-by-token/item-access-by-token-page.component.ts b/src/app/item-page/access-by-token/item-access-by-token-page.component.ts deleted file mode 100644 index 5250ac37a2..0000000000 --- a/src/app/item-page/access-by-token/item-access-by-token-page.component.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { - AsyncPipe, - KeyValuePipe, - Location, - NgForOf, - NgIf, -} from '@angular/common'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy, - OnInit, - PLATFORM_ID, -} from '@angular/core'; -import { - ActivatedRoute, - Router, - RouterLink, -} from '@angular/router'; -import { TranslateModule } from '@ngx-translate/core'; -import { Observable } from 'rxjs'; -import { - filter, - map, - switchMap, - take, - tap, -} from 'rxjs/operators'; - -import { getForbiddenRoute } from '../../app-routing-paths'; -import { AuthService } from '../../core/auth/auth.service'; -import { NotifyInfoService } from '../../core/coar-notify/notify-info/notify-info.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { ItemDataService } from '../../core/data/item-data.service'; -import { ItemRequestDataService } from '../../core/data/item-request-data.service'; -import { SignpostingDataService } from '../../core/data/signposting-data.service'; -import { LinkHeadService } from '../../core/services/link-head.service'; -import { ServerResponseService } from '../../core/services/server-response.service'; -import { redirectOn4xx } from '../../core/shared/authorized.operators'; -import { ItemRequest } from '../../core/shared/item-request.model'; -import { - getFirstCompletedRemoteData, - getFirstSucceededRemoteDataPayload, -} from '../../core/shared/operators'; -import { fadeInOut } from '../../shared/animations/fade'; -import { DsoEditMenuComponent } from '../../shared/dso-page/dso-edit-menu/dso-edit-menu.component'; -import { hasValue } from '../../shared/empty.util'; -import { ErrorComponent } from '../../shared/error/error.component'; -import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; -import { MetadataFieldWrapperComponent } from '../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; -import { ThemedResultsBackButtonComponent } from '../../shared/results-back-button/themed-results-back-button.component'; -import { VarDirective } from '../../shared/utils/var.directive'; -import { ViewTrackerComponent } from '../../statistics/angulartics/dspace/view-tracker.component'; -import { ThemedThumbnailComponent } from '../../thumbnail/themed-thumbnail.component'; -import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component'; -import { CollectionsComponent } from '../field-components/collections/collections.component'; -import { ThemedFullFileSectionComponent } from '../full/field-components/file-section/themed-full-file-section.component'; -import { ThemedMediaViewerComponent } from '../media-viewer/themed-media-viewer.component'; -import { MiradorViewerComponent } from '../mirador-viewer/mirador-viewer.component'; -import { ThemedFileSectionComponent } from '../simple/field-components/file-section/themed-file-section.component'; -import { ItemPageAbstractFieldComponent } from '../simple/field-components/specific-field/abstract/item-page-abstract-field.component'; -import { ItemPageDateFieldComponent } from '../simple/field-components/specific-field/date/item-page-date-field.component'; -import { GenericItemPageFieldComponent } from '../simple/field-components/specific-field/generic/generic-item-page-field.component'; -import { ThemedItemPageTitleFieldComponent } from '../simple/field-components/specific-field/title/themed-item-page-field.component'; -import { ItemPageUriFieldComponent } from '../simple/field-components/specific-field/uri/item-page-uri-field.component'; -import { ItemPageComponent } from '../simple/item-page.component'; -import { ThemedMetadataRepresentationListComponent } from '../simple/metadata-representation-list/themed-metadata-representation-list.component'; -import { ItemVersionsComponent } from '../versions/item-versions.component'; -import { ItemVersionsNoticeComponent } from '../versions/notice/item-versions-notice.component'; -import { ItemSecureFileSectionComponent } from './field-components/file-section/item-secure-file-section.component'; -import { ItemAccessByTokenViewComponent } from './item-access-by-token-view.component'; - -@Component({ - selector: 'ds-access-by-token-item-page', - styleUrls: ['./item-access-by-token-page.component.scss'], - templateUrl: './item-access-by-token-page.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - animations: [fadeInOut], - standalone: true, - imports: [ - ErrorComponent, - ThemedLoadingComponent, - TranslateModule, - ThemedFullFileSectionComponent, - CollectionsComponent, - ItemVersionsComponent, - NgIf, - NgForOf, - AsyncPipe, - KeyValuePipe, - RouterLink, - ThemedItemPageTitleFieldComponent, - DsoEditMenuComponent, - ItemVersionsNoticeComponent, - ViewTrackerComponent, - ThemedItemAlertsComponent, - VarDirective, - ItemSecureFileSectionComponent, - GenericItemPageFieldComponent, - ItemPageAbstractFieldComponent, - ItemPageDateFieldComponent, - ItemPageUriFieldComponent, - MetadataFieldWrapperComponent, - MiradorViewerComponent, - ThemedFileSectionComponent, - ThemedMediaViewerComponent, - ThemedMetadataRepresentationListComponent, - ThemedResultsBackButtonComponent, - ThemedThumbnailComponent, - ItemAccessByTokenViewComponent, - ], -}) -export class ItemAccessByTokenPageComponent extends ItemPageComponent implements OnInit, OnDestroy { - - itemRequest$: Observable; - - constructor( - protected route: ActivatedRoute, - protected router: Router, - protected items: ItemDataService, - protected authService: AuthService, - protected authorizationService: AuthorizationDataService, - protected _location: Location, - protected responseService: ServerResponseService, - protected signpostingDataService: SignpostingDataService, - protected linkHeadService: LinkHeadService, - protected notifyInfoService: NotifyInfoService, - private itemRequestDataService: ItemRequestDataService, - @Inject(PLATFORM_ID) protected platformId: string, - ) { - super(route, router, items, authorizationService, responseService, signpostingDataService, linkHeadService, notifyInfoService, platformId); - } - - protected readonly hasValue = hasValue; - - /** - * Initialise this component - * 1. take the access token from the query params and complete the stream - * 2. test for access token or redirect to forbidden page - * 3. get the sanitized token, make sure it is valid (if not, redirect to forbidden page) - * 4. return observable to itemRequest$ for the view to subscribe to - */ - ngOnInit(): void { - this.itemRequest$ = this.route.queryParams.pipe( - take(1), - map(params => { - if (!hasValue(params?.accessToken)) { - this.router.navigateByUrl(getForbiddenRoute(), { skipLocationChange: false }); - return null; - } - return params.accessToken; - }), - filter(token => hasValue(token)), - switchMap(token => this.itemRequestDataService.getSanitizedRequestByAccessToken(token)), - getFirstCompletedRemoteData(), - redirectOn4xx(this.router, this.authService), - getFirstSucceededRemoteDataPayload(), - tap(request => { - if (!hasValue(request)) { - this.router.navigateByUrl(getForbiddenRoute()); - } - }), - ); - - // Call item page component initialization. - super.ngOnInit(); - } - - /** - * Navigate back in browser history. - */ - back() { - this._location.back(); - } - -} - diff --git a/src/app/item-page/access-by-token/item-access-by-token-view.component.html b/src/app/item-page/access-by-token/item-access-by-token-view.component.html deleted file mode 100644 index 047966e615..0000000000 --- a/src/app/item-page/access-by-token/item-access-by-token-view.component.html +++ /dev/null @@ -1,101 +0,0 @@ - - -
-
- - -
-
- -
- - - -
-
-

{{'bitstream-request-a-copy.access-by-token.warning' | translate}}

-

{{ 'bitstream-request-a-copy.access-by-token.expiry-label' | translate }} {{ getAccessPeriodEndDate() }}

-
-
-
- - - - - -
- - -
- - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
diff --git a/src/app/item-page/access-by-token/item-access-by-token-view.component.scss b/src/app/item-page/access-by-token/item-access-by-token-view.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/item-page/access-by-token/item-access-by-token-view.component.spec.ts b/src/app/item-page/access-by-token/item-access-by-token-view.component.spec.ts deleted file mode 100644 index 78e4544b6c..0000000000 --- a/src/app/item-page/access-by-token/item-access-by-token-view.component.spec.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { - ComponentFixture, - TestBed, - waitForAsync, -} from '@angular/core/testing'; -import { ActivatedRoute } from '@angular/router'; -import { provideMockStore } from '@ngrx/store/testing'; -import { TranslateModule } from '@ngx-translate/core'; -import { - of as observableOf, - of, -} from 'rxjs'; - -import { - APP_CONFIG, - APP_DATA_SERVICES_MAP, -} from '../../../config/app-config.interface'; -import { environment } from '../../../environments/environment'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { ItemRequestDataService } from '../../core/data/item-request-data.service'; -import { Bitstream } from '../../core/shared/bitstream.model'; -import { Item } from '../../core/shared/item.model'; -import { ItemRequest } from '../../core/shared/item-request.model'; -import { ITEM_REQUEST } from '../../core/shared/item-request.resource-type'; -import { DsoEditMenuComponent } from '../../shared/dso-page/dso-edit-menu/dso-edit-menu.component'; -import { ErrorComponent } from '../../shared/error/error.component'; -import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; -import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; -import { ThemedResultsBackButtonComponent } from '../../shared/results-back-button/themed-results-back-button.component'; -import { RouterLinkDirectiveStub } from '../../shared/testing/router-link-directive.stub'; -import { ViewTrackerComponent } from '../../statistics/angulartics/dspace/view-tracker.component'; -import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component'; -import { CollectionsComponent } from '../field-components/collections/collections.component'; -import { ThemedFullFileSectionComponent } from '../full/field-components/file-section/themed-full-file-section.component'; -import { ThemedMediaViewerComponent } from '../media-viewer/themed-media-viewer.component'; -import { MiradorViewerComponent } from '../mirador-viewer/mirador-viewer.component'; -import { ThemedFileSectionComponent } from '../simple/field-components/file-section/themed-file-section.component'; -import { ThemedMetadataRepresentationListComponent } from '../simple/metadata-representation-list/themed-metadata-representation-list.component'; -import { ItemVersionsComponent } from '../versions/item-versions.component'; -import { ItemVersionsNoticeComponent } from '../versions/notice/item-versions-notice.component'; -import { ItemSecureFileDownloadLinkComponent } from './field-components/file-download-link/item-secure-file-download-link.component'; -import { ItemSecureFileSectionComponent } from './field-components/file-section/item-secure-file-section.component'; -import { ItemSecureMediaViewerComponent } from './field-components/media-viewer/item-secure-media-viewer.component'; -import { ItemAccessByTokenViewComponent } from './item-access-by-token-view.component'; - - -describe('ItemAccessByTokenViewComponent', () => { - let authorizationService: AuthorizationDataService; - let itemRequestDataService: ItemRequestDataService; - let bitstream: Bitstream; - let item: Item; - let itemRequest: ItemRequest; - let component: ItemAccessByTokenViewComponent; - let fixture: ComponentFixture; - let routeStub: any; - - function init() { - itemRequestDataService = jasmine.createSpyObj('itemRequestDataService', { - canDownload: observableOf(true), - }); - bitstream = Object.assign(new Bitstream(), { - uuid: 'bitstreamUuid', - }); - item = Object.assign(new Item(), { - uuid: 'itemUuid', - metadata: { - 'dspace.entity.type': [ - { - value: 'Publication', - }, - ], - }, - _links: { - self: { href: 'obj-selflink' }, - }, - }); - routeStub = { - data: observableOf({ - dso: createSuccessfulRemoteDataObject(item), - }), - children: [], - }; - - const mockItemRequest: ItemRequest = Object.assign(new ItemRequest(), { - - }); - itemRequest = Object.assign(new ItemRequest(), - { - itemId: item.uuid, - bitstreamId: bitstream.uuid, - allfiles: false, - requestEmail: 'user@name.org', - requestName: 'User Name', - requestMessage: 'I would like to request a copy', - accessPeriod: 3600, - decisionDate: new Date().toISOString(), - token: 'test-token', - type: ITEM_REQUEST, - requestDate: new Date().toISOString(), - accessToken: 'test-token', - expires: null, - acceptRequest: true, - }); - } - - function initTestbed() { - - TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), ItemSecureFileDownloadLinkComponent, - RouterLinkDirectiveStub, - ], - providers: [ - { provide: AuthorizationDataService, useValue: authorizationService }, - { provide: ActivatedRoute, useValue: routeStub }, - { provide: RouterLinkDirectiveStub }, - { provide: ItemRequestDataService, useValue: itemRequestDataService }, - provideMockStore(), - { provide: APP_DATA_SERVICES_MAP, useValue: {} }, - { provide: APP_CONFIG, useValue: environment }, - ], - schemas: [NO_ERRORS_SCHEMA], - }).overrideComponent(ItemAccessByTokenViewComponent, { - remove: { - imports: [ - ErrorComponent, - ThemedLoadingComponent, - ThemedFullFileSectionComponent, - CollectionsComponent, - ItemVersionsComponent, - DsoEditMenuComponent, - ItemVersionsNoticeComponent, - ViewTrackerComponent, - ThemedItemAlertsComponent, - ItemSecureFileSectionComponent, - MiradorViewerComponent, - ThemedFileSectionComponent, - ThemedMediaViewerComponent, - ThemedMetadataRepresentationListComponent, - ThemedResultsBackButtonComponent, - ItemSecureMediaViewerComponent, - ], - }, - }).compileComponents(); - } - - const mockItem = Object.assign(new Item(), { - uuid: 'test-item-uuid', - id: 'test-item-id', - }); - - - - - beforeEach(waitForAsync(() => { - init(); - authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(true), - }); - initTestbed(); - })); - beforeEach(() => { - fixture = TestBed.createComponent(ItemAccessByTokenViewComponent); - component = fixture.componentInstance; - component.object = item; - component.itemRequest$ = of(itemRequest); - component.itemRequestSubject.next(itemRequest); - fixture.detectChanges(); - }); - - describe('Component and inputs initialised properly', () => { - it('should initialize with valid ItemRequest input', () => { - //component.itemRequestSubject.next(itemRequest); - component.itemRequest$.subscribe(request => { - expect(request).toBeDefined(); - expect(request.accessPeriod).toBe(3600); - expect(request.token).toBe('test-token'); - expect(request.requestName).toBe('User Name'); - expect(request.requestEmail).toBe('user@name.org'); - expect(request.requestMessage).toBe('I would like to request a copy'); - expect(request.allfiles).toBe(false); - expect(request.bitstreamId).toBe(bitstream.uuid); - expect(request.acceptRequest).toBe(true); - }); - }); - }); - - describe('getAccessPeriodEndDate', () => { - it('should calculate correct end date based on decision date and access period', () => { - const testDecisionDate = '2024-01-01T00:00:00Z'; - const testAccessPeriod = 3600; - - const testRequest = { - ...itemRequest, - decisionDate: testDecisionDate, - accessPeriod: testAccessPeriod, - }; - component.itemRequest$ = of(testRequest); - component.itemRequestSubject.next(testRequest); - const expectedDate = new Date(testDecisionDate); - expectedDate.setUTCSeconds(expectedDate.getUTCSeconds() + testAccessPeriod); - - expect(component.getAccessPeriodEndDate()).toEqual(expectedDate); - }); - - it('should return undefined when access period is 0', () => { - component.itemRequestSubject.next({ ...itemRequest, accessPeriod: 0 }); - expect(component.getAccessPeriodEndDate()).toBeUndefined(); - }); - }); -}); - diff --git a/src/app/item-page/access-by-token/item-access-by-token-view.component.ts b/src/app/item-page/access-by-token/item-access-by-token-view.component.ts deleted file mode 100644 index 61f7679cfc..0000000000 --- a/src/app/item-page/access-by-token/item-access-by-token-view.component.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { - AsyncPipe, - KeyValuePipe, - NgForOf, - NgIf, -} from '@angular/common'; -import { - Component, - Input, - OnInit, -} from '@angular/core'; -import { - Router, - RouterLink, -} from '@angular/router'; -import { TranslateModule } from '@ngx-translate/core'; -import { - BehaviorSubject, - Observable, -} from 'rxjs'; -import { filter } from 'rxjs/operators'; - -import { RouteService } from '../../core/services/route.service'; -import { ItemRequest } from '../../core/shared/item-request.model'; -import { DsoEditMenuComponent } from '../../shared/dso-page/dso-edit-menu/dso-edit-menu.component'; -import { hasValue } from '../../shared/empty.util'; -import { ErrorComponent } from '../../shared/error/error.component'; -import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; -import { MetadataFieldWrapperComponent } from '../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; -import { ThemedResultsBackButtonComponent } from '../../shared/results-back-button/themed-results-back-button.component'; -import { VarDirective } from '../../shared/utils/var.directive'; -import { ViewTrackerComponent } from '../../statistics/angulartics/dspace/view-tracker.component'; -import { ThemedThumbnailComponent } from '../../thumbnail/themed-thumbnail.component'; -import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component'; -import { CollectionsComponent } from '../field-components/collections/collections.component'; -import { ThemedFullFileSectionComponent } from '../full/field-components/file-section/themed-full-file-section.component'; -import { ThemedMediaViewerComponent } from '../media-viewer/themed-media-viewer.component'; -import { MiradorViewerComponent } from '../mirador-viewer/mirador-viewer.component'; -import { ThemedFileSectionComponent } from '../simple/field-components/file-section/themed-file-section.component'; -import { ItemPageAbstractFieldComponent } from '../simple/field-components/specific-field/abstract/item-page-abstract-field.component'; -import { ItemPageDateFieldComponent } from '../simple/field-components/specific-field/date/item-page-date-field.component'; -import { GenericItemPageFieldComponent } from '../simple/field-components/specific-field/generic/generic-item-page-field.component'; -import { ThemedItemPageTitleFieldComponent } from '../simple/field-components/specific-field/title/themed-item-page-field.component'; -import { ItemPageUriFieldComponent } from '../simple/field-components/specific-field/uri/item-page-uri-field.component'; -import { ItemComponent } from '../simple/item-types/shared/item.component'; -import { ThemedMetadataRepresentationListComponent } from '../simple/metadata-representation-list/themed-metadata-representation-list.component'; -import { ItemVersionsComponent } from '../versions/item-versions.component'; -import { ItemVersionsNoticeComponent } from '../versions/notice/item-versions-notice.component'; -import { ItemSecureFileSectionComponent } from './field-components/file-section/item-secure-file-section.component'; -import { ItemSecureMediaViewerComponent } from './field-components/media-viewer/item-secure-media-viewer.component'; - -@Component({ - selector: 'ds-item-access-by-token-view', - styleUrls: ['./item-access-by-token-view.component.scss'], - templateUrl: './item-access-by-token-view.component.html', - standalone: true, - imports: [ - ErrorComponent, - ThemedLoadingComponent, - TranslateModule, - ThemedFullFileSectionComponent, - CollectionsComponent, - ItemVersionsComponent, - NgIf, - NgForOf, - AsyncPipe, - KeyValuePipe, - RouterLink, - ThemedItemPageTitleFieldComponent, - DsoEditMenuComponent, - ItemVersionsNoticeComponent, - ViewTrackerComponent, - ThemedItemAlertsComponent, - VarDirective, - ItemSecureFileSectionComponent, - GenericItemPageFieldComponent, - ItemPageAbstractFieldComponent, - ItemPageDateFieldComponent, - ItemPageUriFieldComponent, - MetadataFieldWrapperComponent, - MiradorViewerComponent, - ThemedFileSectionComponent, - ThemedMediaViewerComponent, - ThemedMetadataRepresentationListComponent, - ThemedResultsBackButtonComponent, - ThemedThumbnailComponent, - ItemSecureMediaViewerComponent, - //ItemPageTitleFieldComponent, - //ThumbnailComponent, - //MetadataRepresentationListComponent, - ], -}) -export class ItemAccessByTokenViewComponent extends ItemComponent implements OnInit { - - @Input() itemRequest$: Observable; - itemRequestSubject = new BehaviorSubject(null); - expiryDate: Date; - - constructor( - protected routeService: RouteService, - protected router: Router, - ) { - super(routeService, router); - } - - protected readonly hasValue = hasValue; - - ngOnInit(): void { - this.itemRequest$.pipe( - filter(request => hasValue(request)), - ).subscribe(request => { - this.itemRequestSubject.next(request); - super.ngOnInit(); - }); - - - } - - getAccessPeriodEndDate(): Date { - const request = this.itemRequestSubject.getValue(); - // Set expiry, if not 0 - if (hasValue(request) && request.accessPeriod > 0) { - const date = new Date(request.decisionDate); - date.setUTCSeconds(date.getUTCSeconds() + request.accessPeriod); - return date; - } - } -} diff --git a/src/app/item-page/item-page-routes.ts b/src/app/item-page/item-page-routes.ts index 12ddcba701..632d979457 100644 --- a/src/app/item-page/item-page-routes.ts +++ b/src/app/item-page/item-page-routes.ts @@ -1,6 +1,7 @@ import { Route } from '@angular/router'; import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths'; +import { accessTokenResolver } from '../core/auth/access-token.resolver'; import { authenticatedGuard } from '../core/auth/authenticated.guard'; import { itemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver'; import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; @@ -21,13 +22,13 @@ import { orcidPageGuard } from './orcid-page/orcid-page.guard'; import { ThemedItemPageComponent } from './simple/themed-item-page.component'; import { versionResolver } from './version-page/version.resolver'; import { VersionPageComponent } from './version-page/version-page/version-page.component'; -import { accessTokenResolver } from '../core/auth/access-token.resolver'; export const ROUTES: Route[] = [ { path: ':id', resolve: { dso: itemPageResolver, + itemRequest: accessTokenResolver, breadcrumb: itemBreadcrumbResolver, }, runGuardsAndResolvers: 'always', diff --git a/src/app/item-page/media-viewer/media-viewer-image/media-viewer-image.component.ts b/src/app/item-page/media-viewer/media-viewer-image/media-viewer-image.component.ts index 3acb14d3ee..cf1fd64855 100644 --- a/src/app/item-page/media-viewer/media-viewer-image/media-viewer-image.component.ts +++ b/src/app/item-page/media-viewer/media-viewer-image/media-viewer-image.component.ts @@ -15,6 +15,7 @@ import { Observable } from 'rxjs'; import { AuthService } from '../../../core/auth/auth.service'; import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model'; +import { hasValue } from '../../../shared/empty.util'; /** * This component render an image gallery for the image viewer @@ -99,7 +100,7 @@ export class MediaViewerImageComponent implements OnChanges, OnInit { medium: image.thumbnail ? image.thumbnail : this.thumbnailPlaceholder, - big: image.bitstream._links.content.href, + big: image.bitstream._links.content.href + (hasValue(image.accessToken) ? ('?accessToken=' + image.accessToken) : ''), }); } } diff --git a/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html b/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html index a3d87c780d..d9ddcbf6d9 100644 --- a/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html +++ b/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html @@ -1,6 +1,6 @@