mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +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",
|
"sortablejs": "1.10.1",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
"webfontloader": "1.6.28",
|
"webfontloader": "1.6.28",
|
||||||
"zone.js": "^0.10.3"
|
"zone.js": "^0.10.3",
|
||||||
|
"@kolkov/ngx-gallery": "^1.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-builders/custom-webpack": "10.0.1",
|
"@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 { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
|
||||||
import { ThemedItemPageComponent } from './simple/themed-item-page.component';
|
import { ThemedItemPageComponent } from './simple/themed-item-page.component';
|
||||||
import { ThemedFullItemPageComponent } from './full/themed-full-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 = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -54,6 +58,9 @@ const DECLARATIONS = [
|
|||||||
ItemComponent,
|
ItemComponent,
|
||||||
UploadBitstreamComponent,
|
UploadBitstreamComponent,
|
||||||
AbstractIncrementalListComponent,
|
AbstractIncrementalListComponent,
|
||||||
|
MediaViewerComponent,
|
||||||
|
MediaViewerVideoComponent,
|
||||||
|
MediaViewerImageComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -64,7 +71,8 @@ const DECLARATIONS = [
|
|||||||
EditItemPageModule,
|
EditItemPageModule,
|
||||||
StatisticsModule.forRoot(),
|
StatisticsModule.forRoot(),
|
||||||
JournalEntitiesModule.withEntryComponents(),
|
JournalEntitiesModule.withEntryComponents(),
|
||||||
ResearchEntitiesModule.withEntryComponents()
|
ResearchEntitiesModule.withEntryComponents(),
|
||||||
|
NgxGalleryModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
...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>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<ng-container *ngIf="!mediaViewer.image">
|
||||||
<ds-metadata-field-wrapper>
|
<ds-metadata-field-wrapper>
|
||||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||||
</ds-metadata-field-wrapper>
|
</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-file-section [item]="object"></ds-item-page-file-section>
|
||||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||||
<ds-item-page-author-field [item]="object"></ds-item-page-author-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 { Component, Input, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../../../../environments/environment';
|
||||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
@@ -20,6 +21,7 @@ export class ItemComponent implements OnInit {
|
|||||||
* Route to the item page
|
* Route to the item page
|
||||||
*/
|
*/
|
||||||
itemPageRoute: string;
|
itemPageRoute: string;
|
||||||
|
mediaViewer = environment.mediaViewer;
|
||||||
|
|
||||||
constructor(protected bitstreamDataService: BitstreamDataService) {
|
constructor(protected bitstreamDataService: BitstreamDataService) {
|
||||||
}
|
}
|
||||||
|
@@ -8,9 +8,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<ng-container *ngIf="!mediaViewer.image">
|
||||||
<ds-metadata-field-wrapper>
|
<ds-metadata-field-wrapper>
|
||||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||||
</ds-metadata-field-wrapper>
|
</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-file-section [item]="object"></ds-item-page-file-section>
|
||||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||||
<ds-item-page-author-field [item]="object"></ds-item-page-author-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",
|
"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-email.title": "New user registration",
|
||||||
|
|
||||||
"register-page.create-profile.header": "Create Profile",
|
"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 { ThemeConfig } from './theme.model';
|
||||||
import { AuthConfig } from './auth-config.interfaces';
|
import { AuthConfig } from './auth-config.interfaces';
|
||||||
import { UIServerConfig } from './ui-server-config.interface';
|
import { UIServerConfig } from './ui-server-config.interface';
|
||||||
|
import { MediaViewerConfig } from './media-viewer-config.interface';
|
||||||
|
|
||||||
export interface GlobalConfig extends Config {
|
export interface GlobalConfig extends Config {
|
||||||
ui: UIServerConfig;
|
ui: UIServerConfig;
|
||||||
@@ -32,4 +33,5 @@ export interface GlobalConfig extends Config {
|
|||||||
collection: CollectionPageConfig;
|
collection: CollectionPageConfig;
|
||||||
themes: ThemeConfig[];
|
themes: ThemeConfig[];
|
||||||
rewriteDownloadUrls: boolean;
|
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
|
// 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,
|
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',
|
name: 'base',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
mediaViewer: {
|
||||||
|
image: true,
|
||||||
|
video: true
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@@ -35,10 +35,6 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ds-header-navbar-wrapper {
|
|
||||||
z-index: var(--ds-nav-z-index);
|
|
||||||
}
|
|
||||||
|
|
||||||
ds-admin-sidebar {
|
ds-admin-sidebar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: var(--ds-sidebar-z-index);
|
z-index: var(--ds-sidebar-z-index);
|
||||||
@@ -47,3 +43,16 @@ ds-admin-sidebar {
|
|||||||
.ds-full-screen-loader {
|
.ds-full-screen-loader {
|
||||||
height: 100vh;
|
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"
|
merge-source-map "^1.1.0"
|
||||||
schema-utils "^2.7.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":
|
"@ng-bootstrap/ng-bootstrap@7.0.0":
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-7.0.0.tgz#3bfa62eb52fdb891b1ce693ea11c39127e2d1ab7"
|
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-7.0.0.tgz#3bfa62eb52fdb891b1ce693ea11c39127e2d1ab7"
|
||||||
|
Reference in New Issue
Block a user