mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge pull request #888 from qultoltd/#885-media-viewer
#885 media viewer
This commit is contained in:
@@ -122,7 +122,8 @@
|
||||
"sortablejs": "1.10.1",
|
||||
"tslib": "^2.0.0",
|
||||
"webfontloader": "1.6.28",
|
||||
"zone.js": "^0.10.3"
|
||||
"zone.js": "^0.10.3",
|
||||
"@kolkov/ngx-gallery": "^1.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-builders/custom-webpack": "10.0.1",
|
||||
|
@@ -27,6 +27,10 @@ import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal
|
||||
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
|
||||
import { ThemedItemPageComponent } from './simple/themed-item-page.component';
|
||||
import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component';
|
||||
import { MediaViewerComponent } from './media-viewer/media-viewer.component';
|
||||
import { MediaViewerVideoComponent } from './media-viewer/media-viewer-video/media-viewer-video.component';
|
||||
import { MediaViewerImageComponent } from './media-viewer/media-viewer-image/media-viewer-image.component';
|
||||
import { NgxGalleryModule } from '@kolkov/ngx-gallery';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
// put only entry components that use custom decorator
|
||||
@@ -54,6 +58,9 @@ const DECLARATIONS = [
|
||||
ItemComponent,
|
||||
UploadBitstreamComponent,
|
||||
AbstractIncrementalListComponent,
|
||||
MediaViewerComponent,
|
||||
MediaViewerVideoComponent,
|
||||
MediaViewerImageComponent
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -64,7 +71,8 @@ const DECLARATIONS = [
|
||||
EditItemPageModule,
|
||||
StatisticsModule.forRoot(),
|
||||
JournalEntitiesModule.withEntryComponents(),
|
||||
ResearchEntitiesModule.withEntryComponents()
|
||||
ResearchEntitiesModule.withEntryComponents(),
|
||||
NgxGalleryModule,
|
||||
],
|
||||
declarations: [
|
||||
...DECLARATIONS
|
||||
|
@@ -0,0 +1,7 @@
|
||||
<div [class.change-gallery]="isAuthenticated$ | async">
|
||||
<ngx-gallery
|
||||
class="ngx-gallery"
|
||||
[options]="galleryOptions"
|
||||
[images]="galleryImages"
|
||||
></ngx-gallery>
|
||||
</div>
|
@@ -0,0 +1,6 @@
|
||||
.ngx-gallery {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
width: 340px !important;
|
||||
height: 279px !important;
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NgxGalleryOptions } from '@kolkov/ngx-gallery';
|
||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
||||
import { MockBitstreamFormat1 } from '../../../shared/mocks/item.mock';
|
||||
|
||||
import { MediaViewerImageComponent } from './media-viewer-image.component';
|
||||
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { AuthService } from '../../../core/auth/auth.service';
|
||||
|
||||
describe('MediaViewerImageComponent', () => {
|
||||
let component: MediaViewerImageComponent;
|
||||
let fixture: ComponentFixture<MediaViewerImageComponent>;
|
||||
|
||||
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<MediaViewerItem>(),
|
||||
[
|
||||
{ bitstream: mockBitstream, format: 'image', thumbnail: null },
|
||||
{ bitstream: mockBitstream, format: 'image', thumbnail: null },
|
||||
]
|
||||
);
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports:[],
|
||||
declarations: [MediaViewerImageComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
providers: [
|
||||
{ provide: AuthService, useValue: authService },
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MediaViewerImageComponent);
|
||||
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);
|
||||
});
|
||||
});
|
@@ -0,0 +1,88 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { NgxGalleryImage, NgxGalleryOptions } from '@kolkov/ngx-gallery';
|
||||
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
||||
import { NgxGalleryAnimation } from '@kolkov/ngx-gallery';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthService } from '../../../core/auth/auth.service';
|
||||
|
||||
/**
|
||||
* This componenet render an image gallery for the image viewer
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-media-viewer-image',
|
||||
templateUrl: './media-viewer-image.component.html',
|
||||
styleUrls: ['./media-viewer-image.component.scss'],
|
||||
})
|
||||
export class MediaViewerImageComponent implements OnInit {
|
||||
@Input() images: MediaViewerItem[];
|
||||
@Input() preview?: boolean;
|
||||
@Input() image?: string;
|
||||
|
||||
loggedin: boolean;
|
||||
|
||||
galleryOptions: NgxGalleryOptions[];
|
||||
galleryImages: NgxGalleryImage[];
|
||||
|
||||
/**
|
||||
* Whether or not the current user is authenticated
|
||||
*/
|
||||
isAuthenticated$: Observable<boolean>;
|
||||
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
/**
|
||||
* Thi method sets up the gallery settings and data
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.isAuthenticated$ = this.authService.isAuthenticated();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method convert an array of MediaViewerItem into NgxGalleryImage array
|
||||
* @param medias input NgxGalleryImage array
|
||||
*/
|
||||
convertToGalleryImage(medias: MediaViewerItem[]): NgxGalleryImage[] {
|
||||
const mappadImages = [];
|
||||
for (const image of medias) {
|
||||
if (image.format === 'image') {
|
||||
mappadImages.push({
|
||||
small: image.thumbnail
|
||||
? image.thumbnail
|
||||
: './assets/images/replacement_image.svg',
|
||||
medium: image.thumbnail
|
||||
? image.thumbnail
|
||||
: './assets/images/replacement_image.svg',
|
||||
big: image.bitstream._links.content.href,
|
||||
});
|
||||
}
|
||||
}
|
||||
return mappadImages;
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
<video
|
||||
#media
|
||||
[src]="filteredMedias[currentIndex].bitstream._links.content.href"
|
||||
id="singleVideo"
|
||||
[poster]="
|
||||
filteredMedias[currentIndex].thumbnail ||
|
||||
replacements[filteredMedias[currentIndex].format]
|
||||
"
|
||||
preload="none"
|
||||
controls
|
||||
></video>
|
||||
<div class="buttons" *ngIf="filteredMedias?.length > 1">
|
||||
<button
|
||||
class="btn btn-primary previous"
|
||||
[disabled]="currentIndex === 0"
|
||||
(click)="prevMedia()"
|
||||
>
|
||||
{{ "media-viewer.previous" | translate }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-primary next"
|
||||
[disabled]="currentIndex === filteredMedias.length - 1"
|
||||
(click)="nextMedia()"
|
||||
>
|
||||
{{ "media-viewer.next" | translate }}
|
||||
</button>
|
||||
<div ngbDropdown class="d-inline-block">
|
||||
<button
|
||||
class="btn btn-outline-primary playlist"
|
||||
id="dropdownBasic1"
|
||||
ngbDropdownToggle
|
||||
>
|
||||
{{ "media-viewer.playlist" | translate }}
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
|
||||
<button
|
||||
ngbDropdownItem
|
||||
*ngFor="let item of filteredMedias; index as indexOfelement"
|
||||
class="list-element"
|
||||
(click)="selectedMedia(indexOfelement)"
|
||||
>
|
||||
{{ item.bitstream.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,4 @@
|
||||
video {
|
||||
width: 340px;
|
||||
height: 279px;
|
||||
}
|
@@ -0,0 +1,145 @@
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
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 { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock';
|
||||
import { FileSizePipe } from '../../../shared/utils/file-size-pipe';
|
||||
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||
import { MetadataFieldWrapperComponent } from '../../field-components/metadata-field-wrapper/metadata-field-wrapper.component';
|
||||
import { MockBitstreamFormat1 } from '../../../shared/mocks/item.mock';
|
||||
import { MediaViewerVideoComponent } from './media-viewer-video.component';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
describe('MediaViewerVideoComponent', () => {
|
||||
let component: MediaViewerVideoComponent;
|
||||
let fixture: ComponentFixture<MediaViewerVideoComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock,
|
||||
},
|
||||
}),
|
||||
BrowserAnimationsModule,
|
||||
],
|
||||
declarations: [
|
||||
MediaViewerVideoComponent,
|
||||
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<MediaViewerItem>(),
|
||||
[
|
||||
{ bitstream: mockBitstream, format: 'video', thumbnail: null },
|
||||
{ bitstream: mockBitstream, format: 'video', thumbnail: null },
|
||||
]
|
||||
);
|
||||
const mockMediaViewerItem: MediaViewerItem[] = Object.assign(
|
||||
new Array<MediaViewerItem>(),
|
||||
[{ bitstream: mockBitstream, format: 'video', thumbnail: null }]
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MediaViewerVideoComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.medias = mockMediaViewerItem;
|
||||
component.filteredMedias = 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;
|
||||
component.filteredMedias = 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,55 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
||||
|
||||
/**
|
||||
* This componenet renders a video viewer and playlist for the media viewer
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-media-viewer-video',
|
||||
templateUrl: './media-viewer-video.component.html',
|
||||
styleUrls: ['./media-viewer-video.component.scss'],
|
||||
})
|
||||
export class MediaViewerVideoComponent implements OnInit {
|
||||
@Input() medias: MediaViewerItem[];
|
||||
|
||||
filteredMedias: MediaViewerItem[];
|
||||
|
||||
isCollapsed: boolean;
|
||||
currentIndex = 0;
|
||||
|
||||
replacements = {
|
||||
video: './assets/images/replacement_video.svg',
|
||||
audio: './assets/images/replacement_audio.svg',
|
||||
};
|
||||
|
||||
replacementThumbnail: string;
|
||||
|
||||
ngOnInit() {
|
||||
this.isCollapsed = false;
|
||||
this.filteredMedias = this.medias.filter(
|
||||
(media) => media.format === 'audio' || media.format === 'video'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the reviced index into currentIndex
|
||||
* @param index Selected index
|
||||
*/
|
||||
selectedMedia(index: number) {
|
||||
this.currentIndex = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method increade the number of the currentIndex
|
||||
*/
|
||||
nextMedia() {
|
||||
this.currentIndex++;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method decrese the number of the currentIndex
|
||||
*/
|
||||
prevMedia() {
|
||||
this.currentIndex--;
|
||||
}
|
||||
}
|
36
src/app/+item-page/media-viewer/media-viewer.component.html
Normal file
36
src/app/+item-page/media-viewer/media-viewer.component.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<ng-container *ngVar="mediaList$ | async as mediaList">
|
||||
<ds-loading
|
||||
*ngIf="isLoading"
|
||||
message="{{ 'loading.default' | translate }}"
|
||||
[showMessage]="false"
|
||||
></ds-loading>
|
||||
<div class="media-viewer" *ngIf="!isLoading">
|
||||
<ng-container *ngIf="mediaList.length > 0">
|
||||
<ng-container *ngIf="videoOptions">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
mediaList[0]?.format === 'video' || mediaList[0]?.format === 'audio'
|
||||
"
|
||||
>
|
||||
<ds-media-viewer-video [medias]="mediaList"></ds-media-viewer-video>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="mediaList[0]?.format === 'image'">
|
||||
<ds-media-viewer-image [images]="mediaList"></ds-media-viewer-image>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
((mediaList[0]?.format !== 'image') &&
|
||||
(!videoOptions || mediaList[0]?.format !== 'video') &&
|
||||
(!videoOptions || mediaList[0]?.format !== 'audio')) ||
|
||||
mediaList.length === 0
|
||||
"
|
||||
>
|
||||
<ds-media-viewer-image
|
||||
[image]="mediaList[0]?.thumbnail || thumbnailPlaceholder"
|
||||
[preview]="false"
|
||||
></ds-media-viewer-image>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
@@ -0,0 +1 @@
|
||||
|
143
src/app/+item-page/media-viewer/media-viewer.component.spec.ts
Normal file
143
src/app/+item-page/media-viewer/media-viewer.component.spec.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { MediaViewerComponent } from './media-viewer.component';
|
||||
import { MockBitstreamFormat1 } from '../../shared/mocks/item.mock';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { MediaViewerItem } from '../../core/shared/media-viewer-item.model';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { MetadataFieldWrapperComponent } from '../field-components/metadata-field-wrapper/metadata-field-wrapper.component';
|
||||
import { FileSizePipe } from '../../shared/utils/file-size-pipe';
|
||||
|
||||
describe('MediaViewerComponent', () => {
|
||||
let comp: MediaViewerComponent;
|
||||
let fixture: ComponentFixture<MediaViewerComponent>;
|
||||
|
||||
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(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock,
|
||||
},
|
||||
}),
|
||||
BrowserAnimationsModule,
|
||||
],
|
||||
declarations: [
|
||||
MediaViewerComponent,
|
||||
VarDirective,
|
||||
FileSizePipe,
|
||||
MetadataFieldWrapperComponent,
|
||||
],
|
||||
providers: [
|
||||
{ provide: BitstreamDataService, useValue: bitstreamDataService },
|
||||
],
|
||||
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MediaViewerComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('when the bitstreams are loading', () => {
|
||||
beforeEach(() => {
|
||||
comp.mediaList$.next([mockMediaViewerItem]);
|
||||
comp.videoOptions = 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.videoOptions = 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-media-viewer-image')
|
||||
);
|
||||
expect(defaultThumbnail.nativeElement).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
114
src/app/+item-page/media-viewer/media-viewer.component.ts
Normal file
114
src/app/+item-page/media-viewer/media-viewer.component.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { filter, take } from 'rxjs/operators';
|
||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
|
||||
import { Bitstream } from '../../core/shared/bitstream.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 { followLink } from '../../shared/utils/follow-link-config.model';
|
||||
|
||||
/**
|
||||
* This componenet renders the media viewers
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-media-viewer',
|
||||
templateUrl: './media-viewer.component.html',
|
||||
styleUrls: ['./media-viewer.component.scss'],
|
||||
})
|
||||
export class MediaViewerComponent implements OnInit {
|
||||
@Input() item: Item;
|
||||
@Input() videoOptions: boolean;
|
||||
|
||||
mediaList$: BehaviorSubject<MediaViewerItem[]>;
|
||||
|
||||
isLoading: boolean;
|
||||
|
||||
thumbnailPlaceholder = './assets/images/replacement_document.svg';
|
||||
|
||||
constructor(protected bitstreamDataService: BitstreamDataService) {}
|
||||
|
||||
/**
|
||||
* This metod loads all the Bitstreams and Thumbnails and contert it to media item
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.mediaList$ = new BehaviorSubject([]);
|
||||
this.isLoading = true;
|
||||
this.loadRemoteData('ORIGINAL').subscribe((bitstreamsRD) => {
|
||||
if (bitstreamsRD.payload.page.length === 0) {
|
||||
this.isLoading = false;
|
||||
this.mediaList$.next([]);
|
||||
} else {
|
||||
this.loadRemoteData('THUMBNAIL').subscribe((thumbnailsRD) => {
|
||||
for (
|
||||
let index = 0;
|
||||
index < bitstreamsRD.payload.page.length;
|
||||
index++
|
||||
) {
|
||||
bitstreamsRD.payload.page[index].format
|
||||
.pipe(getFirstSucceededRemoteDataPayload())
|
||||
.subscribe((format) => {
|
||||
const current = this.mediaList$.getValue();
|
||||
const mediaItem = this.createMediaViewerItem(
|
||||
bitstreamsRD.payload.page[index],
|
||||
format,
|
||||
thumbnailsRD.payload && thumbnailsRD.payload.page[index]
|
||||
);
|
||||
this.mediaList$.next([...current, mediaItem]);
|
||||
});
|
||||
}
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will retrieve the next page of Bitstreams from the external BitstreamDataService call.
|
||||
* @param bundleName Bundle name
|
||||
*/
|
||||
loadRemoteData(
|
||||
bundleName: string
|
||||
): Observable<RemoteData<PaginatedList<Bitstream>>> {
|
||||
return this.bitstreamDataService
|
||||
.findAllByItemAndBundleName(
|
||||
this.item,
|
||||
bundleName,
|
||||
{},
|
||||
true,
|
||||
true,
|
||||
followLink('format')
|
||||
)
|
||||
.pipe(
|
||||
filter(
|
||||
(bitstreamsRD: RemoteData<PaginatedList<Bitstream>>) =>
|
||||
hasValue(bitstreamsRD) &&
|
||||
(hasValue(bitstreamsRD.errorMessage) || hasValue(bitstreamsRD.payload))
|
||||
),
|
||||
take(1)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method create MediaViewerItem from incoming bitstreams
|
||||
* @param original original remote data bitstream
|
||||
* @param format original bitstream format
|
||||
* @param thumbnail trunbnail remote data bitstream
|
||||
*/
|
||||
createMediaViewerItem(
|
||||
original: Bitstream,
|
||||
format: BitstreamFormat,
|
||||
thumbnail: Bitstream
|
||||
): MediaViewerItem {
|
||||
const mediaItem = new MediaViewerItem();
|
||||
mediaItem.bitstream = original;
|
||||
mediaItem.format = format.mimetype.split('/')[0];
|
||||
mediaItem.thumbnail = thumbnail ? thumbnail._links.content.href : null;
|
||||
return mediaItem;
|
||||
}
|
||||
}
|
@@ -8,9 +8,14 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ng-container *ngIf="!mediaViewer.image">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="mediaViewer.image">
|
||||
<ds-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-media-viewer>
|
||||
</ng-container>
|
||||
<ds-item-page-file-section [item]="object"></ds-item-page-file-section>
|
||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||
<ds-item-page-author-field [item]="object"></ds-item-page-author-field>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../../../environments/environment';
|
||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
@@ -20,6 +21,7 @@ export class ItemComponent implements OnInit {
|
||||
* Route to the item page
|
||||
*/
|
||||
itemPageRoute: string;
|
||||
mediaViewer = environment.mediaViewer;
|
||||
|
||||
constructor(protected bitstreamDataService: BitstreamDataService) {
|
||||
}
|
||||
|
@@ -8,9 +8,14 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ng-container *ngIf="!mediaViewer.image">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="mediaViewer.image">
|
||||
<ds-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-media-viewer>
|
||||
</ng-container>
|
||||
<ds-item-page-file-section [item]="object"></ds-item-page-file-section>
|
||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||
<ds-item-page-author-field [item]="object"></ds-item-page-author-field>
|
||||
|
21
src/app/core/shared/media-viewer-item.model.ts
Normal file
21
src/app/core/shared/media-viewer-item.model.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Bitstream } from './bitstream.model';
|
||||
|
||||
/**
|
||||
* Model representing a media viewer item
|
||||
*/
|
||||
export class MediaViewerItem {
|
||||
/**
|
||||
* Incoming Bitsream
|
||||
*/
|
||||
bitstream: Bitstream;
|
||||
|
||||
/**
|
||||
* Incoming Bitsream format type
|
||||
*/
|
||||
format: string;
|
||||
|
||||
/**
|
||||
* Incoming Bitsream thumbnail
|
||||
*/
|
||||
thumbnail: string;
|
||||
}
|
@@ -2546,6 +2546,13 @@
|
||||
"publication.search.title": "DSpace Angular :: Publication Search",
|
||||
|
||||
|
||||
"media-viewer.next": "Next",
|
||||
|
||||
"media-viewer.previous": "Previous",
|
||||
|
||||
"media-viewer.playlist": "Playlist",
|
||||
|
||||
|
||||
"register-email.title": "New user registration",
|
||||
|
||||
"register-page.create-profile.header": "Create Profile",
|
||||
|
9
src/assets/images/replacement_audio.svg
Normal file
9
src/assets/images/replacement_audio.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="240" height="180" viewBox="0 0 240 180" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect width="240" height="180" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0" transform="scale(0.00416667 0.00555556)"/>
|
||||
</pattern>
|
||||
<image id="image0" width="240" height="180" xlink:href="data:image/png;base64,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"/>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
9
src/assets/images/replacement_document.svg
Normal file
9
src/assets/images/replacement_document.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="240" height="180" viewBox="0 0 240 180" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect width="240" height="180" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0" transform="scale(0.00416667 0.00555556)"/>
|
||||
</pattern>
|
||||
<image id="image0" width="240" height="180" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAAC0CAYAAACqnKHoAAAESElEQVR4nO3dX2saWQDG4dNYFzFIQ7YhVLoV8v0/z/bamxBp1yiiSEPTclyE0otl0ZiZ13ke8H5mnJ9z5s8Z3yweH38UINKFrw1yCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCCRiCvfXltctiuSyb9bo8PT2d3boNh8MyGo3KYDBowdKchzeLx8cfXd8IbbBarcpsNivfzjDc310Oh2U8Hpd+v9+uBQsk4Bao4f4zn3dqnXsXF2UymTgaH8k5cMPqkLlr8Vbfn5/LdDo9y1OF1yTgBtWdd/bw0Nn1rxHf39+3YElyCbhB8/l8txN32XqzKdvtttPb4BgCblC9cIXtcAwBN6gLV5z/j81m0/6FbCkBQzABQzABQzABQzABQzCTGYLcvH9fbm5uWr/Af3/+3IKl6AZHYAgmYAgmYAjmHPhAdSbN+j+eIKpzXut0OTglR2AIJmAIJmAI5hw4yJevX3cf2HMEhmAChmAChmAChmAChmAChmAChmDuAwepz1fXPwhrO/eqX4+Ag9R4Eyb0C/j1GEJDMAFDMAFDMAFDMAFDMAFDMLeRgtQ/BPdPfvxKwEEWy+XuA3uG0BBMwBBMwBBMwBBMwBBMwBBMwBDMfeAgV+/elaurq5Mu8MNsVrbb7ZltufMl4CD9fv/kb+ToXRiUJfFtQTABQzABQzABQzABQzABQzC3kYIsl8uTT+h3DziLgIN8e3rafWDPEBqCCRiCCRiCCRiCCbhBJg78q07S4DD2oAYNLy87u+6/sh0OJ+AG/Xl93dl13/uj39/Nc+YwAm5Qnds7Go06u/7V7e1tC5Yil4AbNv7woQwGg06uex2BdP0H7FgCbliv1yuTT586tSPXi3fj8djR9wV4lLIFasR/ffy4e855sViU1WpVvj8/n9161vPd+kN1fX3tyvMLEXCL1HPiU7/zivNiCA3BBAzBBAzBBAzBBAzBBAzBBAzB3Ac+kfpyuOl0epbr9rvJZNKuBeoQAZ9IfZJqfeI3SIIh9IE8MUUbCPhAZtHQBgI+UJ0CeOkoTMMEfIQ6Jc57rWiSve8IdUpcvQIrYppizztSHUrf3d0ZTtMIt5FewP5IXO/91sn4p/4DMtgT8AuqR+Ouvt+KZhhCQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQ6pSyk/71rEEHrIkFQAAAABJRU5ErkJggg=="/>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
9
src/assets/images/replacement_image.svg
Normal file
9
src/assets/images/replacement_image.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="240" height="180" viewBox="0 0 240 180" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect width="240" height="180" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0" transform="scale(0.00416667 0.00555556)"/>
|
||||
</pattern>
|
||||
<image id="image0" width="240" height="180" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAAC0CAYAAACqnKHoAAAE5UlEQVR4nO3d70pbSRzH4eNaxKB06RLJ4iIVBO//hrwAqWysWOIbcZlAwLX+SeIkZ745z/OmFmoxYz6e8xsnunc7nT51QKQ/fNogl4AhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAhmIAh2BefvM15fHzs7n/96h5ms/nbs9lsVx/q/4xGo25/f787HI2646Oj+dtshoA35Pr6uvtxczMPd2jKF62FEu/JeNxNJpPBrcM27N1Op0+7/zC3pwR7dXXVzR4ehvKQlzI6POwuLi5cjSszA1cm3teVNSlrQ10CrqjcNov3bWVtyhpRj4ArKbfOZeblfUPdF9gUAVdSNm48MT+22JmnDgFX8jCQbxHVYK3qEXAlrr70QcCVDOWQBm0RMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMAQTMHN/fftmIQJ9GfoC0HV/TybdZDKZ/4rUn3d3ViSIK/DA/fn16zze4uzsrDs4OBj6kkQR8ICVWEu0C/v7+9359+9DX5YoAh6oRazlz+dGo1H3z+np0JcnhoAH6vT0dB7ra8bj8fzWmvYJOMDo8LDqB1l2nD/adTYPZxBw48qV8PLyslrE5f95Pve+xTycQcANex7bxcXFp6+I8yjPz5f+9+bh9gm4USW2Eu9ik+mtTadVlPdf9YuAebhtAm7Ua5tM5e/L3P6+phzWOD4+Xut9zcPtEnCDTsbjNzeZytVw1dva54c11mEebpeAG1Pm3tMPAh2/E/hLLw9rrMs83CYBN6Rc6cpm1TJKlB/tTNeYm58zD7dHwA1ZNbaPdqbfO6yxLvNwWwTciHJ7uuom03tX2GUOa6zDPNwWATeg3JaW29N1vLYzvexhjXWZh9sh4J7ViO35zvSqhzXWZR5ug4B79PKwxmcsdqa3OaOah/sn4B7V3mQqQW3zqmge7p+Ae/LeYY0k5uF+CbgHyxzWSGIe7o+At2yVwxpJzMP9EPCW1TwZ1RLzcD/8WNktWsy89/f3O/0Y/51OG/hIhkHAW1Se2J7c1OQWGoIJGIIJGIIJuJLaL9uDZQi4kl381hDtE3Alh67AS7NW9Qi4kuOjI1fhJZQ1cuyyHgFXUp6YJ2u+KH9IrFFdAq6o/OjW2r/HaJeUtfnMj7fldwKurLxQQcS/K2uyiy/i6Nve7XT6NOwl2Izr6+vux81N9/j4uIsPb2mL0cKVdzMEvGE/7+66h9lspx/jW8pusw2rzRIwBDMDQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQzABQ6qu6/4DYMqyrZ0wRgUAAAAASUVORK5CYII="/>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
9
src/assets/images/replacement_video.svg
Normal file
9
src/assets/images/replacement_video.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="240" height="180" viewBox="0 0 240 180" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect width="240" height="180" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0" transform="scale(0.00416667 0.00555556)"/>
|
||||
</pattern>
|
||||
<image id="image0" width="240" height="180" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAAC0CAYAAACqnKHoAAAFZ0lEQVR4nO3dbU/UWByH4QqLMRCQQBACMfL9v5JZYiRr0AkPgQwxEHTzn41GdxfsYGeYX3tdiS98Y9oDt+2ZOT19dnF+/rUBIi35sUEuAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUMwAUOwP/zwHu/29ra5vLpqvtzdNZ8/f27u7u5ST2WulpeXmxcvXjRLy8vNxvp6s7KyMqCz79azi/Pzr306oXmocD+NRs3FxUX/T3YONjc3m1c7O0J+BAFPqa607969a+6+fIk67kW3vLTUHB4eTq7MtGcOPAXxzk6NaY1tjTHtCXgKf334IN4ZqrGtMaY9Abd0dXXl6jAHNcY11rQj4JYu/VLNjQ8H2xNwS9fjccRx9oE7nfYE3NLN7W3EcfaBsW7PQo6O1cKEnZ0dX4f8S11VR6ORqUjHXIE7VAsSXr9+Ld7/UWNSY1NjRHcE3JFaiLC3u9uLc5mlGqMaK7phJDtSV5ha48vDvq2DphsC7sja2lovzoMsAoZgAoZgAoZgAoZgAoZgAoZgllIGqqd1Ts/OJssTa1HE+saGLWkGSsBh6oH3Hx+3q4fg6+9Xl5fN9vZ2s7W1ZUHJgLiFDjIej+99VrZCro32jo6OPE87IAIOcn19/cuDrUfx6iptf6lhEHCQaYIcX183fx4d/bOPl/2qe8scOMhjQvxxflzPKdMvrsAD8G1+/PbtWxvG9YyAB6Tmx++Pj82Pe0TAA2R+3B/mwANmfpzPFXjgfpwfu63OI2Aman58fHzsljqMgPmuIvYpdRYB85Nbm6pHETAEEzA/WV1dNSBBBMx39dYE2+Nm8T0wE9tbW83e3p7BCCPggVtbXW0ODg7s5hFKwAP1fGWl2d/fd8scTsADU3to7bx6NbllJp+AB6SirTXP9szqDwEPgHlufwk4yLRXTvPc/hNwkHqv7mWLtcrmucMh4CC10OL09HTyCOB9zHOHRcBBag5biy1OTk7+E7F57jAJOMy35Y51K10P4Nc8t9Yvm+cOk4AD1VXW/JbGwwyQTcAQTMAQTMAQTMAQTMAdubEZHE9AwB25vbnpxXmQRcAdqfcN2ZL112qMxi1eVE47Au7Qe282eFCNTY0R3bESq0O1tLHeMVTLHdfX13tzXl2oNz7Uy9QeehCD6Qm4Y/ULenp2NvnD49TjkLRjpFqqZ3Ex1otGwC2teWPB3Ai4PQG3VC/BxlgvGgG35BG++agxtilBewKeQm1V4/Zudmpsa4xpT8BTqH2mDt+8aTZ8RdS5GtMaW3t5TefZxfn516QDXhT1vean0Wjy3S+PVx8O1pzX9+aPI+DfVKuLZhXxycePT/4fRN3W7u3uzuzfdsX9PRZy/Kb6BZzVhnKLsKChjsGGeYvLHBiCCRiCCRiCCXiBrTx//uQHtwjHwP0EvMA2X7588oNbhGPgfgJeYPXp78H+/pMd4IFXky483wMHqG1o6mH4eapNCaxJXnwChmBuoSGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCGYgCFV0zR/A9c/gdi8GSXTAAAAAElFTkSuQmCC"/>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
@@ -12,6 +12,7 @@ import { CollectionPageConfig } from './collection-page-config.interface';
|
||||
import { ThemeConfig } from './theme.model';
|
||||
import { AuthConfig } from './auth-config.interfaces';
|
||||
import { UIServerConfig } from './ui-server-config.interface';
|
||||
import { MediaViewerConfig } from './media-viewer-config.interface';
|
||||
|
||||
export interface GlobalConfig extends Config {
|
||||
ui: UIServerConfig;
|
||||
@@ -32,4 +33,5 @@ export interface GlobalConfig extends Config {
|
||||
collection: CollectionPageConfig;
|
||||
themes: ThemeConfig[];
|
||||
rewriteDownloadUrls: boolean;
|
||||
mediaViewer: MediaViewerConfig;
|
||||
}
|
||||
|
6
src/config/media-viewer-config.interface.ts
Normal file
6
src/config/media-viewer-config.interface.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Config } from './config.interface';
|
||||
|
||||
export interface MediaViewerConfig extends Config {
|
||||
image: boolean;
|
||||
video: boolean;
|
||||
}
|
@@ -263,4 +263,11 @@ export const environment: GlobalConfig = {
|
||||
],
|
||||
// Whether the UI should rewrite file download URLs to match its domain. Only necessary to enable when running UI and REST API on separate domains
|
||||
rewriteDownloadUrls: false,
|
||||
// Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with "image" or "video").
|
||||
// For images, this enables a gallery viewer where you can zoom or page through images.
|
||||
// For videos, this enables embedded video streaming
|
||||
mediaViewer: {
|
||||
image: false,
|
||||
video: false,
|
||||
},
|
||||
};
|
||||
|
@@ -217,4 +217,8 @@ export const environment: Partial<GlobalConfig> = {
|
||||
name: 'base',
|
||||
},
|
||||
],
|
||||
mediaViewer: {
|
||||
image: true,
|
||||
video: true
|
||||
},
|
||||
};
|
||||
|
@@ -35,10 +35,6 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ds-header-navbar-wrapper {
|
||||
z-index: var(--ds-nav-z-index);
|
||||
}
|
||||
|
||||
ds-admin-sidebar {
|
||||
position: fixed;
|
||||
z-index: var(--ds-sidebar-z-index);
|
||||
@@ -47,3 +43,16 @@ ds-admin-sidebar {
|
||||
.ds-full-screen-loader {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.sticky-top {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.media-viewer
|
||||
.change-gallery
|
||||
.ngx-gallery
|
||||
ngx-gallery-preview.ngx-gallery-active {
|
||||
right: 0;
|
||||
left: auto;
|
||||
width: calc(100% - 53px);
|
||||
}
|
||||
|
@@ -1247,6 +1247,11 @@
|
||||
merge-source-map "^1.1.0"
|
||||
schema-utils "^2.7.0"
|
||||
|
||||
"@kolkov/ngx-gallery@^1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@kolkov/ngx-gallery/-/ngx-gallery-1.2.3.tgz#bb15d4b64a5c03905677aa4ca741835aabe41f43"
|
||||
integrity sha512-Dpnhwq3DGPSXrNt65gexo+/Smb2L0bne14A0WONN04+racETtcY33fqFvNWfRw5Nvk2Eza+sq95eEA0NbgF/6g==
|
||||
|
||||
"@ng-bootstrap/ng-bootstrap@7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-7.0.0.tgz#3bfa62eb52fdb891b1ce693ea11c39127e2d1ab7"
|
||||
|
Reference in New Issue
Block a user