mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Merge pull request #1079 from mspalti/iiif-mirador
Angular support for IIIF and Mirador
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
"serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
|
"serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
|
||||||
"start:dev": "npm-run-all --parallel config:dev:watch serve",
|
"start:dev": "npm-run-all --parallel config:dev:watch serve",
|
||||||
"start:prod": "yarn run build:prod && yarn run serve:ssr",
|
"start:prod": "yarn run build:prod && yarn run serve:ssr",
|
||||||
|
"start:mirador:prod": "yarn run build:mirador && yarn run start:prod",
|
||||||
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
|
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"build:stats": "ng build --stats-json",
|
"build:stats": "ng build --stats-json",
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
"clean": "yarn run clean:prod && yarn run clean:env && yarn run clean:node",
|
"clean": "yarn run clean:prod && yarn run clean:env && yarn run clean:node",
|
||||||
"clean:env": "rimraf src/environments/environment.ts",
|
"clean:env": "rimraf src/environments/environment.ts",
|
||||||
"sync-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts",
|
"sync-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts",
|
||||||
|
"build:mirador": "webpack --config webpack/webpack.mirador.config.ts",
|
||||||
"merge-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts",
|
"merge-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts",
|
||||||
"postinstall": "ngcc",
|
"postinstall": "ngcc",
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
@@ -106,6 +108,9 @@
|
|||||||
"jsonschema": "1.4.0",
|
"jsonschema": "1.4.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"klaro": "^0.7.10",
|
"klaro": "^0.7.10",
|
||||||
|
"mirador": "^3.0.0",
|
||||||
|
"mirador-dl-plugin": "^0.13.0",
|
||||||
|
"mirador-share-plugin": "^0.10.0",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ng-mocks": "10.5.4",
|
"ng-mocks": "10.5.4",
|
||||||
@@ -118,6 +123,8 @@
|
|||||||
"nouislider": "^14.6.3",
|
"nouislider": "^14.6.3",
|
||||||
"pem": "1.14.4",
|
"pem": "1.14.4",
|
||||||
"postcss-cli": "^8.3.0",
|
"postcss-cli": "^8.3.0",
|
||||||
|
"react": "^16.14.0",
|
||||||
|
"react-dom": "^16.14.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^6.6.3",
|
"rxjs": "^6.6.3",
|
||||||
"rxjs-spy": "^7.5.3",
|
"rxjs-spy": "^7.5.3",
|
||||||
@@ -157,6 +164,7 @@
|
|||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.0.3",
|
"fork-ts-checker-webpack-plugin": "^6.0.3",
|
||||||
|
"html-loader": "^1.3.2",
|
||||||
"html-webpack-plugin": "^4.5.0",
|
"html-webpack-plugin": "^4.5.0",
|
||||||
"http-proxy-middleware": "^1.0.5",
|
"http-proxy-middleware": "^1.0.5",
|
||||||
"jasmine-core": "^3.6.0",
|
"jasmine-core": "^3.6.0",
|
||||||
|
@@ -41,6 +41,8 @@ import { UIServerConfig } from './src/config/ui-server-config.interface';
|
|||||||
* Set path for the browser application's dist folder
|
* Set path for the browser application's dist folder
|
||||||
*/
|
*/
|
||||||
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
|
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
|
||||||
|
// Set path fir IIIF viewer.
|
||||||
|
const IIIF_VIEWER = join(process.cwd(), 'dist/iiif');
|
||||||
|
|
||||||
const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : 'index';
|
const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : 'index';
|
||||||
|
|
||||||
@@ -135,6 +137,10 @@ export function app() {
|
|||||||
* Serve static resources (images, i18n messages, …)
|
* Serve static resources (images, i18n messages, …)
|
||||||
*/
|
*/
|
||||||
server.get('*.*', cacheControl, express.static(DIST_FOLDER, { index: false }));
|
server.get('*.*', cacheControl, express.static(DIST_FOLDER, { index: false }));
|
||||||
|
/*
|
||||||
|
* Fallthrough to the IIIF viewer (must be included in the build).
|
||||||
|
*/
|
||||||
|
server.use('/iiif', express.static(IIIF_VIEWER, {index:false}));
|
||||||
|
|
||||||
// Register the ngApp callback function to handle incoming requests
|
// Register the ngApp callback function to handle incoming requests
|
||||||
server.get('*', ngApp);
|
server.get('*', ngApp);
|
||||||
|
@@ -28,6 +28,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-dat
|
|||||||
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
||||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||||
import { JournalComponent } from './journal.component';
|
import { JournalComponent } from './journal.component';
|
||||||
|
import { RouteService } from '../../../../core/services/route.service';
|
||||||
|
|
||||||
let comp: JournalComponent;
|
let comp: JournalComponent;
|
||||||
let fixture: ComponentFixture<JournalComponent>;
|
let fixture: ComponentFixture<JournalComponent>;
|
||||||
@@ -86,6 +87,7 @@ describe('JournalComponent', () => {
|
|||||||
{ provide: NotificationsService, useValue: {} },
|
{ provide: NotificationsService, useValue: {} },
|
||||||
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
||||||
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||||
|
{ provide: RouteService, useValue: {} }
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -12,7 +12,6 @@ import { ItemPageAbstractFieldComponent } from './simple/field-components/specif
|
|||||||
import { ItemPageUriFieldComponent } from './simple/field-components/specific-field/uri/item-page-uri-field.component';
|
import { ItemPageUriFieldComponent } from './simple/field-components/specific-field/uri/item-page-uri-field.component';
|
||||||
import { ItemPageTitleFieldComponent } from './simple/field-components/specific-field/title/item-page-title-field.component';
|
import { ItemPageTitleFieldComponent } from './simple/field-components/specific-field/title/item-page-title-field.component';
|
||||||
import { ItemPageFieldComponent } from './simple/field-components/specific-field/item-page-field.component';
|
import { ItemPageFieldComponent } from './simple/field-components/specific-field/item-page-field.component';
|
||||||
import { FileSectionComponent } from './simple/field-components/file-section/file-section.component';
|
|
||||||
import { CollectionsComponent } from './field-components/collections/collections.component';
|
import { CollectionsComponent } from './field-components/collections/collections.component';
|
||||||
import { FullItemPageComponent } from './full/full-item-page.component';
|
import { FullItemPageComponent } from './full/full-item-page.component';
|
||||||
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
|
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
|
||||||
@@ -31,10 +30,12 @@ import { MediaViewerComponent } from './media-viewer/media-viewer.component';
|
|||||||
import { MediaViewerVideoComponent } from './media-viewer/media-viewer-video/media-viewer-video.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 { MediaViewerImageComponent } from './media-viewer/media-viewer-image/media-viewer-image.component';
|
||||||
import { NgxGalleryModule } from '@kolkov/ngx-gallery';
|
import { NgxGalleryModule } from '@kolkov/ngx-gallery';
|
||||||
|
import { MiradorViewerComponent } from './mirador-viewer/mirador-viewer.component';
|
||||||
import { VersionPageComponent } from './version-page/version-page/version-page.component';
|
import { VersionPageComponent } from './version-page/version-page/version-page.component';
|
||||||
import { VersionedItemComponent } from './simple/item-types/versioned-item/versioned-item.component';
|
import { VersionedItemComponent } from './simple/item-types/versioned-item/versioned-item.component';
|
||||||
import { ThemedFileSectionComponent} from './simple/field-components/file-section/themed-file-section.component';
|
import { ThemedFileSectionComponent} from './simple/field-components/file-section/themed-file-section.component';
|
||||||
|
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
PublicationComponent,
|
PublicationComponent,
|
||||||
@@ -54,7 +55,6 @@ const DECLARATIONS = [
|
|||||||
ItemPageUriFieldComponent,
|
ItemPageUriFieldComponent,
|
||||||
ItemPageTitleFieldComponent,
|
ItemPageTitleFieldComponent,
|
||||||
ItemPageFieldComponent,
|
ItemPageFieldComponent,
|
||||||
FileSectionComponent,
|
|
||||||
CollectionsComponent,
|
CollectionsComponent,
|
||||||
FullFileSectionComponent,
|
FullFileSectionComponent,
|
||||||
PublicationComponent,
|
PublicationComponent,
|
||||||
@@ -65,6 +65,7 @@ const DECLARATIONS = [
|
|||||||
MediaViewerComponent,
|
MediaViewerComponent,
|
||||||
MediaViewerVideoComponent,
|
MediaViewerVideoComponent,
|
||||||
MediaViewerImageComponent,
|
MediaViewerImageComponent,
|
||||||
|
MiradorViewerComponent,
|
||||||
VersionPageComponent,
|
VersionPageComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
<p class="full-text-op">{{'iiifviewer.fullscreen.notice' | translate}}</p>
|
||||||
|
<p *ngIf="!isViewerAvailable" id="viewer-message">{{viewerMessage}}</p>
|
||||||
|
<iframe title="Mirador Viewer" allowtransparency="true" *ngIf="isViewerAvailable" [src]="iframeViewerUrl | async" id="mirador-viewer"></iframe>
|
||||||
|
|
@@ -0,0 +1,13 @@
|
|||||||
|
#mirador-viewer {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
height: 660px;
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
.full-text-op {
|
||||||
|
text-align: right;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
p.full-text-op {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
@@ -0,0 +1,255 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { MiradorViewerComponent } from './mirador-viewer.component';
|
||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
|
||||||
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
|
import { createRelationshipsObservable } from '../simple/item-types/shared/item.component.spec';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { MetadataMap } from '../../core/shared/metadata.models';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { MiradorViewerService } from './mirador-viewer.service';
|
||||||
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
|
|
||||||
|
|
||||||
|
function getItem(metadata: MetadataMap) {
|
||||||
|
return Object.assign(new Item(), {
|
||||||
|
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
|
metadata: metadata,
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const noMetadata = new MetadataMap();
|
||||||
|
|
||||||
|
const mockHostWindowService = {
|
||||||
|
// This isn't really testing mobile status, the return observable just allows the test to run.
|
||||||
|
widthCategory: observableOf(true),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('MiradorViewerComponent with search', () => {
|
||||||
|
let comp: MiradorViewerComponent;
|
||||||
|
let fixture: ComponentFixture<MiradorViewerComponent>;
|
||||||
|
const viewerService = jasmine.createSpyObj('MiradorViewerService', ['showEmbeddedViewer', 'getImageCount']);
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
viewerService.showEmbeddedViewer.and.returnValue(true);
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: TranslateLoaderMock
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [MiradorViewerComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: BitstreamDataService, useValue: {} },
|
||||||
|
{ provide: HostWindowService, useValue: mockHostWindowService }
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(MiradorViewerComponent, {
|
||||||
|
set: {
|
||||||
|
providers: [
|
||||||
|
{ provide: MiradorViewerService, useValue: viewerService }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
describe('searchable item', () => {
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
fixture = TestBed.createComponent(MiradorViewerComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = getItem(noMetadata);
|
||||||
|
comp.searchable = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should set multi property to true', (() => {
|
||||||
|
expect(comp.multi).toBe(true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should set url "multi" param to true', (() => {
|
||||||
|
const value = fixture.debugElement
|
||||||
|
.nativeElement.querySelector('#mirador-viewer').src;
|
||||||
|
expect(value).toContain('multi=true');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should set url "searchable" param to true', (() => {
|
||||||
|
const value = fixture.debugElement
|
||||||
|
.nativeElement.querySelector('#mirador-viewer').src;
|
||||||
|
expect(value).toContain('searchable=true');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not call mirador service image count', () => {
|
||||||
|
expect(viewerService.getImageCount).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MiradorViewerComponent with multiple images', () => {
|
||||||
|
|
||||||
|
let comp: MiradorViewerComponent;
|
||||||
|
let fixture: ComponentFixture<MiradorViewerComponent>;
|
||||||
|
const viewerService = jasmine.createSpyObj('MiradorViewerService', ['showEmbeddedViewer', 'getImageCount']);
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
viewerService.showEmbeddedViewer.and.returnValue(true);
|
||||||
|
viewerService.getImageCount.and.returnValue(observableOf(2));
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: TranslateLoaderMock
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [MiradorViewerComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: BitstreamDataService, useValue: {} },
|
||||||
|
{ provide: HostWindowService, useValue: mockHostWindowService }
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(MiradorViewerComponent, {
|
||||||
|
set: {
|
||||||
|
providers: [
|
||||||
|
{ provide: MiradorViewerService, useValue: viewerService }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('non-searchable item with multiple images', () => {
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
fixture = TestBed.createComponent(MiradorViewerComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = getItem(noMetadata);
|
||||||
|
comp.searchable = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should set url "multi" param to true', (() => {
|
||||||
|
const value = fixture.debugElement
|
||||||
|
.nativeElement.querySelector('#mirador-viewer').src;
|
||||||
|
expect(value).toContain('multi=true');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should call mirador service image count', () => {
|
||||||
|
expect(viewerService.getImageCount).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should omit "searchable" param from url', (() => {
|
||||||
|
const value = fixture.debugElement
|
||||||
|
.nativeElement.querySelector('#mirador-viewer').src;
|
||||||
|
expect(value).not.toContain('searchable=true');
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('MiradorViewerComponent with a single image', () => {
|
||||||
|
let comp: MiradorViewerComponent;
|
||||||
|
let fixture: ComponentFixture<MiradorViewerComponent>;
|
||||||
|
const viewerService = jasmine.createSpyObj('MiradorViewerService', ['showEmbeddedViewer', 'getImageCount']);
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
viewerService.showEmbeddedViewer.and.returnValue(true);
|
||||||
|
viewerService.getImageCount.and.returnValue(observableOf(1));
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: TranslateLoaderMock
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [MiradorViewerComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: BitstreamDataService, useValue: {} },
|
||||||
|
{ provide: HostWindowService, useValue: mockHostWindowService }
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(MiradorViewerComponent, {
|
||||||
|
set: {
|
||||||
|
providers: [
|
||||||
|
{ provide: MiradorViewerService, useValue: viewerService }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('single image viewer', () => {
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
fixture = TestBed.createComponent(MiradorViewerComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = getItem(noMetadata);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should omit "multi" param', (() => {
|
||||||
|
const value = fixture.debugElement
|
||||||
|
.nativeElement.querySelector('#mirador-viewer').src;
|
||||||
|
expect(value).not.toContain('multi=false');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should call mirador service image count', () => {
|
||||||
|
expect(viewerService.getImageCount).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MiradorViewerComponent in development mode', () => {
|
||||||
|
let comp: MiradorViewerComponent;
|
||||||
|
let fixture: ComponentFixture<MiradorViewerComponent>;
|
||||||
|
const viewerService = jasmine.createSpyObj('MiradorViewerService', ['showEmbeddedViewer', 'getImageCount']);
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
viewerService.showEmbeddedViewer.and.returnValue(false);
|
||||||
|
viewerService.getImageCount.and.returnValue(observableOf(1));
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: TranslateLoaderMock
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [MiradorViewerComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: BitstreamDataService, useValue: {} }
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(MiradorViewerComponent, {
|
||||||
|
set: {
|
||||||
|
providers: [
|
||||||
|
{ provide: MiradorViewerService, useValue: viewerService },
|
||||||
|
{ provide: HostWindowService, useValue: mockHostWindowService }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('embedded viewer', () => {
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
fixture = TestBed.createComponent(MiradorViewerComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = getItem(noMetadata);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not embed the viewer', (() => {
|
||||||
|
const value = fixture.debugElement
|
||||||
|
.nativeElement.querySelector('#mirador-viewer');
|
||||||
|
expect(value).toBeNull();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show message', (() => {
|
||||||
|
const value = fixture.debugElement
|
||||||
|
.nativeElement.querySelector('#viewer-message');
|
||||||
|
expect(value).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
136
src/app/item-page/mirador-viewer/mirador-viewer.component.ts
Normal file
136
src/app/item-page/mirador-viewer/mirador-viewer.component.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
|
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { map, take } from 'rxjs/operators';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
|
import { MiradorViewerService } from './mirador-viewer.service';
|
||||||
|
import { HostWindowService, WidthCategory } from '../../shared/host-window.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-mirador-viewer',
|
||||||
|
styleUrls: ['./mirador-viewer.component.scss'],
|
||||||
|
templateUrl: './mirador-viewer.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
providers: [ MiradorViewerService ]
|
||||||
|
})
|
||||||
|
export class MiradorViewerComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() object: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A previous dspace search query.
|
||||||
|
*/
|
||||||
|
@Input() query: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if searchable.
|
||||||
|
*/
|
||||||
|
@Input() searchable: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides embedded viewer in dev mode.
|
||||||
|
*/
|
||||||
|
isViewerAvailable = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The url for the iframe.
|
||||||
|
*/
|
||||||
|
iframeViewerUrl: Observable<SafeResourceUrl>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the viewer to show or hide thumbnail side navigation menu.
|
||||||
|
*/
|
||||||
|
multi = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the thumbnail navigation menu on smaller viewports.
|
||||||
|
*/
|
||||||
|
notMobile = false;
|
||||||
|
|
||||||
|
viewerMessage = 'Sorry, the Mirador viewer is not currently available in development mode.';
|
||||||
|
|
||||||
|
constructor(private sanitizer: DomSanitizer,
|
||||||
|
private viewerService: MiradorViewerService,
|
||||||
|
private bitstreamDataService: BitstreamDataService,
|
||||||
|
private hostWindowService: HostWindowService,
|
||||||
|
@Inject(PLATFORM_ID) private platformId: any) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the url for the Mirador iframe. Adds parameters for the displaying the search panel, query results,
|
||||||
|
* or multi-page thumbnail navigation.
|
||||||
|
*/
|
||||||
|
setURL() {
|
||||||
|
// The path to the REST manifest endpoint.
|
||||||
|
const manifestApiEndpoint = encodeURIComponent(environment.rest.baseUrl + '/iiif/'
|
||||||
|
+ this.object.id + '/manifest');
|
||||||
|
// The Express path to Mirador viewer.
|
||||||
|
let viewerPath = '/iiif/mirador/index.html?manifest=' + manifestApiEndpoint;
|
||||||
|
if (this.searchable) {
|
||||||
|
// Tell the viewer add search to menu.
|
||||||
|
viewerPath += '&searchable=' + this.searchable;
|
||||||
|
}
|
||||||
|
if (this.query) {
|
||||||
|
// Tell the viewer to execute a search for the query term.
|
||||||
|
viewerPath += '&query=' + this.query;
|
||||||
|
}
|
||||||
|
if (this.multi) {
|
||||||
|
// Tell the viewer to add thumbnail navigation. If searchable, thumbnail navigation is added by default.
|
||||||
|
viewerPath += '&multi=' + this.multi;
|
||||||
|
}
|
||||||
|
if (this.notMobile) {
|
||||||
|
viewerPath += '¬Mobile=true';
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Should the query term be trusted here?
|
||||||
|
return this.sanitizer.bypassSecurityTrustResourceUrl(viewerPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
/**
|
||||||
|
* Initializes the iframe url observable.
|
||||||
|
*/
|
||||||
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
|
||||||
|
// Viewer is not currently available in dev mode so hide it in that case.
|
||||||
|
this.isViewerAvailable = this.viewerService.showEmbeddedViewer();
|
||||||
|
|
||||||
|
// The notMobile property affects the thumbnail navigation
|
||||||
|
// menu by hiding it for smaller viewports. This will not be
|
||||||
|
// responsive to resizing.
|
||||||
|
this.hostWindowService.widthCategory
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe((category: WidthCategory) => {
|
||||||
|
this.notMobile = !(category === WidthCategory.XS || category === WidthCategory.SM);
|
||||||
|
});
|
||||||
|
|
||||||
|
// We need to set the multi property to true if the
|
||||||
|
// item is searchable or when the ORIGINAL bundle contains more
|
||||||
|
// than 1 image. (The multi property determines whether the
|
||||||
|
// Mirador side thumbnail navigation panel is shown.)
|
||||||
|
if (this.searchable) {
|
||||||
|
this.multi = true;
|
||||||
|
const observable = of('');
|
||||||
|
this.iframeViewerUrl = observable.pipe(
|
||||||
|
map((val) => {
|
||||||
|
return this.setURL();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Sets the multi value based on the image count.
|
||||||
|
this.iframeViewerUrl = this.viewerService.getImageCount(this.object, this.bitstreamDataService).pipe(
|
||||||
|
map(c => {
|
||||||
|
if (c > 1) {
|
||||||
|
this.multi = true;
|
||||||
|
}
|
||||||
|
return this.setURL();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
src/app/item-page/mirador-viewer/mirador-viewer.service.ts
Normal file
57
src/app/item-page/mirador-viewer/mirador-viewer.service.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Injectable, isDevMode } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
|
||||||
|
import { last, map, switchMap } from 'rxjs/operators';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
|
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||||
|
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
|
||||||
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
|
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MiradorViewerService {
|
||||||
|
|
||||||
|
LINKS_TO_FOLLOW: FollowLinkConfig<Bitstream>[] = [
|
||||||
|
followLink('format'),
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns boolean to hide viewer when running in development mode.
|
||||||
|
* Needed until it's possible to embed the viewer in development builds.
|
||||||
|
*/
|
||||||
|
showEmbeddedViewer (): boolean {
|
||||||
|
return !isDevMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns observable of the number of images in the ORIGINAL bundle
|
||||||
|
* @param item
|
||||||
|
* @param bitstreamDataService
|
||||||
|
*/
|
||||||
|
getImageCount(item: Item, bitstreamDataService: BitstreamDataService): Observable<number> {
|
||||||
|
let count = 0;
|
||||||
|
return bitstreamDataService.findAllByItemAndBundleName(item, 'ORIGINAL', {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: 10
|
||||||
|
}, true, true, ...this.LINKS_TO_FOLLOW)
|
||||||
|
.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
map((bitstreamsRD: RemoteData<PaginatedList<Bitstream>>) => bitstreamsRD.payload),
|
||||||
|
map((paginatedList: PaginatedList<Bitstream>) => paginatedList.page),
|
||||||
|
switchMap((bitstreams: Bitstream[]) => bitstreams),
|
||||||
|
switchMap((bitstream: Bitstream) => bitstream.format.pipe(
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
map((format: BitstreamFormat) => format)
|
||||||
|
)),
|
||||||
|
map((format: BitstreamFormat) => {
|
||||||
|
if (format.mimetype.includes('image')) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}),
|
||||||
|
last()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,12 @@
|
|||||||
|
<div class="row" *ngIf="iiifEnabled">
|
||||||
|
<div class="col-12">
|
||||||
|
<ds-mirador-viewer id="iiif-viewer"
|
||||||
|
[object]="object"
|
||||||
|
[searchable]="iiifSearchEnabled"
|
||||||
|
[query]="iiifQuery$ | async">
|
||||||
|
</ds-mirador-viewer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<h2 class="item-page-title-field mr-auto">
|
<h2 class="item-page-title-field mr-auto">
|
||||||
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
@@ -4,7 +4,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
||||||
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
|
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
|
||||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||||
@@ -25,15 +25,33 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-dat
|
|||||||
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
||||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||||
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
||||||
import { createRelationshipsObservable } from '../shared/item.component.spec';
|
import {
|
||||||
|
createRelationshipsObservable,
|
||||||
|
iiifEnabled,
|
||||||
|
iiifSearchEnabled, mockRouteService
|
||||||
|
} from '../shared/item.component.spec';
|
||||||
import { PublicationComponent } from './publication.component';
|
import { PublicationComponent } from './publication.component';
|
||||||
import { createPaginatedList } from '../../../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../../../shared/testing/utils.test';
|
||||||
|
import { RouteService } from '../../../../core/services/route.service';
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
const iiifEnabledMap: MetadataMap = {
|
||||||
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
'dspace.iiif.enabled': [iiifEnabled],
|
||||||
metadata: new MetadataMap(),
|
};
|
||||||
relationships: createRelationshipsObservable()
|
|
||||||
});
|
const iiifEnabledWithSearchMap: MetadataMap = {
|
||||||
|
'dspace.iiif.enabled': [iiifEnabled],
|
||||||
|
'iiif.search.enabled': [iiifSearchEnabled],
|
||||||
|
};
|
||||||
|
|
||||||
|
const noMetadata = new MetadataMap();
|
||||||
|
|
||||||
|
function getItem(metadata: MetadataMap) {
|
||||||
|
return Object.assign(new Item(), {
|
||||||
|
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
|
metadata: metadata,
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('PublicationComponent', () => {
|
describe('PublicationComponent', () => {
|
||||||
let comp: PublicationComponent;
|
let comp: PublicationComponent;
|
||||||
@@ -68,6 +86,7 @@ describe('PublicationComponent', () => {
|
|||||||
{ provide: DSOChangeAnalyzer, useValue: {} },
|
{ provide: DSOChangeAnalyzer, useValue: {} },
|
||||||
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
||||||
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||||
|
{ provide: RouteService, useValue: mockRouteService }
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -76,41 +95,81 @@ describe('PublicationComponent', () => {
|
|||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
describe('default view', () => {
|
||||||
fixture = TestBed.createComponent(PublicationComponent);
|
beforeEach(waitForAsync(() => {
|
||||||
comp = fixture.componentInstance;
|
fixture = TestBed.createComponent(PublicationComponent);
|
||||||
comp.object = mockItem;
|
comp = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
comp.object = getItem(noMetadata);
|
||||||
}));
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
it('should contain a component to display the date', () => {
|
it('should contain a component to display the date', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-date-field'));
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-date-field'));
|
||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not contain a metadata only author field', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
|
||||||
|
expect(fields.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a mixed metadata and relationship field for authors', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('.ds-item-page-mixed-author-field'));
|
||||||
|
expect(fields.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the abstract', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-abstract-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the uri', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-uri-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the collections', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-collections'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not contain a metadata only author field', () => {
|
describe('with IIIF viewer', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
|
|
||||||
expect(fields.length).toBe(0);
|
beforeEach(waitForAsync(() => {
|
||||||
|
fixture = TestBed.createComponent(PublicationComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = getItem(iiifEnabledMap);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should contain an iiif viewer component', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-mirador-viewer'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a mixed metadata and relationship field for authors', () => {
|
describe('with IIIF viewer and search', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('.ds-item-page-mixed-author-field'));
|
|
||||||
expect(fields.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain a component to display the abstract', () => {
|
beforeEach(waitForAsync(() => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-abstract-field'));
|
mockRouteService.getPreviousUrl.and.returnValue(of(['/search?q=bird&motivation=painting','/item']));
|
||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
fixture = TestBed.createComponent(PublicationComponent);
|
||||||
});
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = getItem(iiifEnabledWithSearchMap);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
it('should contain a component to display the uri', () => {
|
it('should contain an iiif viewer component', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-uri-field'));
|
const fields = fixture.debugElement.queryAll(By.css('ds-mirador-viewer'));
|
||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should call the RouteService getHistory method', () => {
|
||||||
|
expect(mockRouteService.getPreviousUrl).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('should contain a component to display the collections', () => {
|
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-collections'));
|
|
||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { filter, map } from 'rxjs/operators';
|
||||||
|
import { RouteService } from '../../../../core/services/route.service';
|
||||||
|
|
||||||
|
export const isIiifEnabled = (item: Item) => {
|
||||||
|
return !!item.firstMetadataValue('dspace.iiif.enabled');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isIiifSearchEnabled = (item: Item) => {
|
||||||
|
return !!item.firstMetadataValue('iiif.search.enabled');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if previous route was a dspace search. If
|
||||||
|
* it was, the search term is extracted and subsequently passed
|
||||||
|
* to the mirador viewer component.
|
||||||
|
* @param item the dspace object
|
||||||
|
* @param routeService
|
||||||
|
*/
|
||||||
|
export const getDSpaceQuery = (item: Item, routeService: RouteService): Observable<string> => {
|
||||||
|
return routeService.getPreviousUrl().pipe(
|
||||||
|
filter(r => {
|
||||||
|
return r.includes('/search');
|
||||||
|
}),
|
||||||
|
map(r => {
|
||||||
|
const arr = r.split('&');
|
||||||
|
const q = arr[1];
|
||||||
|
const v = q.split('=');
|
||||||
|
return v[1];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
@@ -30,6 +30,26 @@ import { GenericItemPageFieldComponent } from '../../field-components/specific-f
|
|||||||
import { compareArraysUsing, compareArraysUsingIds } from './item-relationships-utils';
|
import { compareArraysUsing, compareArraysUsingIds } from './item-relationships-utils';
|
||||||
import { ItemComponent } from './item.component';
|
import { ItemComponent } from './item.component';
|
||||||
import { createPaginatedList } from '../../../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../../../shared/testing/utils.test';
|
||||||
|
import { RouteService } from '../../../../core/services/route.service';
|
||||||
|
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
export const iiifEnabled = Object.assign(new MetadataValue(),{
|
||||||
|
'value': 'true',
|
||||||
|
'language': null,
|
||||||
|
'authority': null,
|
||||||
|
'confidence': -1,
|
||||||
|
'place': 0
|
||||||
|
});
|
||||||
|
|
||||||
|
export const iiifSearchEnabled = Object.assign(new MetadataValue(), {
|
||||||
|
'value': 'true',
|
||||||
|
'language': null,
|
||||||
|
'authority': null,
|
||||||
|
'confidence': -1,
|
||||||
|
'place': 0
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mockRouteService = jasmine.createSpyObj('RouteService', ['getPreviousUrl']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a generic test for an item-page-fields component using a mockItem and the type of component
|
* Create a generic test for an item-page-fields component using a mockItem and the type of component
|
||||||
@@ -72,6 +92,7 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
|
|||||||
{ provide: NotificationsService, useValue: {} },
|
{ provide: NotificationsService, useValue: {} },
|
||||||
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
||||||
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||||
|
{ provide: RouteService, useValue: {} }
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -2,6 +2,9 @@ import { Component, Input, OnInit } from '@angular/core';
|
|||||||
import { environment } from '../../../../../environments/environment';
|
import { environment } from '../../../../../environments/environment';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { getItemPageRoute } from '../../../item-page-routing-paths';
|
import { getItemPageRoute } from '../../../item-page-routing-paths';
|
||||||
|
import { RouteService } from '../../../../core/services/route.service';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { getDSpaceQuery, isIiifEnabled, isIiifSearchEnabled } from './item-iiif-utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item',
|
selector: 'ds-item',
|
||||||
@@ -18,9 +21,33 @@ export class ItemComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
itemPageRoute: string;
|
itemPageRoute: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the mirador component.
|
||||||
|
*/
|
||||||
|
iiifEnabled: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to configure search in mirador.
|
||||||
|
*/
|
||||||
|
iiifSearchEnabled: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The query term from the previous dspace search.
|
||||||
|
*/
|
||||||
|
iiifQuery$: Observable<string>;
|
||||||
|
|
||||||
mediaViewer = environment.mediaViewer;
|
mediaViewer = environment.mediaViewer;
|
||||||
|
|
||||||
|
constructor(protected routeService: RouteService) {
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemPageRoute = getItemPageRoute(this.object);
|
this.itemPageRoute = getItemPageRoute(this.object);
|
||||||
|
// check to see if iiif viewer is required.
|
||||||
|
this.iiifEnabled = isIiifEnabled(this.object);
|
||||||
|
this.iiifSearchEnabled = isIiifSearchEnabled(this.object);
|
||||||
|
if (this.iiifSearchEnabled) {
|
||||||
|
this.iiifQuery$ = getDSpaceQuery(this.object, this.routeService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,12 @@
|
|||||||
|
<div class="row" *ngIf="iiifEnabled">
|
||||||
|
<div class="col-12">
|
||||||
|
<ds-mirador-viewer id="iiif-viewer"
|
||||||
|
[object]="object"
|
||||||
|
[searchable]="iiifSearchEnabled"
|
||||||
|
[query]="iiifQuery$ | async">
|
||||||
|
</ds-mirador-viewer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<h2 class="item-page-title-field mr-auto">
|
<h2 class="item-page-title-field mr-auto">
|
||||||
<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
@@ -12,14 +12,12 @@ import { CommunityDataService } from '../../../../core/data/community-data.servi
|
|||||||
import { DefaultChangeAnalyzer } from '../../../../core/data/default-change-analyzer.service';
|
import { DefaultChangeAnalyzer } from '../../../../core/data/default-change-analyzer.service';
|
||||||
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
|
||||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
|
|
||||||
import { RelationshipService } from '../../../../core/data/relationship.service';
|
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||||
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
|
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { MetadataMap } from '../../../../core/shared/metadata.models';
|
import { MetadataMap } from '../../../../core/shared/metadata.models';
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
|
||||||
import { UUIDService } from '../../../../core/shared/uuid.service';
|
import { UUIDService } from '../../../../core/shared/uuid.service';
|
||||||
import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock';
|
import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock';
|
||||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
@@ -27,8 +25,15 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-dat
|
|||||||
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
||||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||||
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
||||||
import { createRelationshipsObservable } from '../shared/item.component.spec';
|
import {
|
||||||
|
createRelationshipsObservable,
|
||||||
|
iiifEnabled,
|
||||||
|
iiifSearchEnabled, mockRouteService
|
||||||
|
} from '../shared/item.component.spec';
|
||||||
import { UntypedItemComponent } from './untyped-item.component';
|
import { UntypedItemComponent } from './untyped-item.component';
|
||||||
|
import { RouteService } from '../../../../core/services/route.service';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { createPaginatedList } from '../../../../shared/testing/utils.test';
|
||||||
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
|
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
|
||||||
import { VersionDataService } from '../../../../core/data/version-data.service';
|
import { VersionDataService } from '../../../../core/data/version-data.service';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
@@ -36,11 +41,25 @@ import { WorkspaceitemDataService } from '../../../../core/submission/workspacei
|
|||||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
import { ItemVersionsSharedService } from '../../../../shared/item/item-versions/item-versions-shared.service';
|
import { ItemVersionsSharedService } from '../../../../shared/item/item-versions/item-versions-shared.service';
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
|
||||||
bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])),
|
const iiifEnabledMap: MetadataMap = {
|
||||||
metadata: new MetadataMap(),
|
'dspace.iiif.enabled': [iiifEnabled],
|
||||||
relationships: createRelationshipsObservable()
|
};
|
||||||
});
|
|
||||||
|
const iiifEnabledWithSearchMap: MetadataMap = {
|
||||||
|
'dspace.iiif.enabled': [iiifEnabled],
|
||||||
|
'iiif.search.enabled': [iiifSearchEnabled],
|
||||||
|
};
|
||||||
|
|
||||||
|
const noMetadata = new MetadataMap();
|
||||||
|
|
||||||
|
function getItem(metadata: MetadataMap) {
|
||||||
|
return Object.assign(new Item(), {
|
||||||
|
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
|
metadata: metadata,
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('UntypedItemComponent', () => {
|
describe('UntypedItemComponent', () => {
|
||||||
let comp: UntypedItemComponent;
|
let comp: UntypedItemComponent;
|
||||||
@@ -84,48 +103,94 @@ describe('UntypedItemComponent', () => {
|
|||||||
{ provide: SearchService, useValue: {} },
|
{ provide: SearchService, useValue: {} },
|
||||||
{ provide: ItemDataService, useValue: {} },
|
{ provide: ItemDataService, useValue: {} },
|
||||||
{ provide: ItemVersionsSharedService, useValue: {} },
|
{ provide: ItemVersionsSharedService, useValue: {} },
|
||||||
|
{ provide: RouteService, useValue: mockRouteService }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(UntypedItemComponent, {
|
}).overrideComponent(UntypedItemComponent, {
|
||||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
describe('default view', () => {
|
||||||
fixture = TestBed.createComponent(UntypedItemComponent);
|
beforeEach(waitForAsync(() => {
|
||||||
comp = fixture.componentInstance;
|
fixture = TestBed.createComponent(UntypedItemComponent);
|
||||||
comp.object = mockItem;
|
comp = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
comp.object = getItem(noMetadata);
|
||||||
}));
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
it('should contain a component to display the date', () => {
|
it('should contain a component to display the date', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-date-field'));
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-date-field'));
|
||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not contain a metadata only author field', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
|
||||||
|
expect(fields.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a mixed metadata and relationship field for authors', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('.ds-item-page-mixed-author-field'));
|
||||||
|
expect(fields.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the abstract', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-abstract-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the uri', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-uri-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the collections', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-collections'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not contain an iiif viewer component', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-mirador-viewer'));
|
||||||
|
expect(fields.length).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not contain a metadata only author field', () => {
|
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
|
describe('with IIIF viewer', () => {
|
||||||
expect(fields.length).toBe(0);
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
fixture = TestBed.createComponent(UntypedItemComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = getItem(iiifEnabledMap);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should contain an iiif viewer component', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-mirador-viewer'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a mixed metadata and relationship field for authors', () => {
|
describe('with IIIF viewer and search', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('.ds-item-page-mixed-author-field'));
|
|
||||||
expect(fields.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain a component to display the abstract', () => {
|
beforeEach(waitForAsync(() => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-abstract-field'));
|
mockRouteService.getPreviousUrl.and.returnValue(of(['/search?q=bird&motivation=painting','/item']));
|
||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
fixture = TestBed.createComponent(UntypedItemComponent);
|
||||||
});
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = getItem(iiifEnabledWithSearchMap);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
it('should contain a component to display the uri', () => {
|
it('should contain an iiif viewer component', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-uri-field'));
|
const fields = fixture.debugElement.queryAll(By.css('ds-mirador-viewer'));
|
||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should call the RouteService getHistory method', () => {
|
||||||
|
expect(mockRouteService.getPreviousUrl).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('should contain a component to display the collections', () => {
|
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-collections'));
|
|
||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -11,13 +11,14 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-dat
|
|||||||
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
|
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { MetadataMap } from '../../../../core/shared/metadata.models';
|
import { MetadataMap } from '../../../../core/shared/metadata.models';
|
||||||
import { createRelationshipsObservable } from '../shared/item.component.spec';
|
import { createRelationshipsObservable, mockRouteService } from '../shared/item.component.spec';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
|
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
|
||||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
import { Version } from '../../../../core/shared/version.model';
|
import { Version } from '../../../../core/shared/version.model';
|
||||||
|
import { RouteService } from '../../../../core/services/route.service';
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])),
|
bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])),
|
||||||
@@ -66,6 +67,7 @@ describe('VersionedItemComponent', () => {
|
|||||||
{ provide: WorkspaceitemDataService, useValue: {} },
|
{ provide: WorkspaceitemDataService, useValue: {} },
|
||||||
{ provide: SearchService, useValue: {} },
|
{ provide: SearchService, useValue: {} },
|
||||||
{ provide: ItemDataService, useValue: {} },
|
{ provide: ItemDataService, useValue: {} },
|
||||||
|
{ provide: RouteService, useValue: mockRouteService }
|
||||||
]
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
versionService = TestBed.inject(VersionDataService);
|
versionService = TestBed.inject(VersionDataService);
|
||||||
|
@@ -16,6 +16,7 @@ import { SearchService } from '../../../../core/shared/search/search.service';
|
|||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
import { WorkspaceItem } from '../../../../core/submission/models/workspaceitem.model';
|
import { WorkspaceItem } from '../../../../core/submission/models/workspaceitem.model';
|
||||||
|
import { RouteService } from '../../../../core/services/route.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-versioned-item',
|
selector: 'ds-versioned-item',
|
||||||
@@ -34,8 +35,9 @@ export class VersionedItemComponent extends ItemComponent {
|
|||||||
private workspaceItemDataService: WorkspaceitemDataService,
|
private workspaceItemDataService: WorkspaceitemDataService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private itemService: ItemDataService,
|
private itemService: ItemDataService,
|
||||||
|
protected routeService: RouteService
|
||||||
) {
|
) {
|
||||||
super();
|
super(routeService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -187,6 +187,7 @@ import { MissingTranslationHelper } from './translate/missing-translation.helper
|
|||||||
import { ItemVersionsNoticeComponent } from './item/item-versions/notice/item-versions-notice.component';
|
import { ItemVersionsNoticeComponent } from './item/item-versions/notice/item-versions-notice.component';
|
||||||
import { FileValidator } from './utils/require-file.validator';
|
import { FileValidator } from './utils/require-file.validator';
|
||||||
import { FileValueAccessorDirective } from './utils/file-value-accessor.directive';
|
import { FileValueAccessorDirective } from './utils/file-value-accessor.directive';
|
||||||
|
import { FileSectionComponent } from '../item-page/simple/field-components/file-section/file-section.component';
|
||||||
import { ExistingRelationListElementComponent } from './form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component';
|
import { ExistingRelationListElementComponent } from './form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component';
|
||||||
import { ModifyItemOverviewComponent } from '../item-page/edit-item-page/modify-item-overview/modify-item-overview.component';
|
import { ModifyItemOverviewComponent } from '../item-page/edit-item-page/modify-item-overview/modify-item-overview.component';
|
||||||
import { ClaimedTaskActionsLoaderComponent } from './mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component';
|
import { ClaimedTaskActionsLoaderComponent } from './mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component';
|
||||||
@@ -327,6 +328,7 @@ const COMPONENTS = [
|
|||||||
DsDatePickerInlineComponent,
|
DsDatePickerInlineComponent,
|
||||||
DsSelectComponent,
|
DsSelectComponent,
|
||||||
ErrorComponent,
|
ErrorComponent,
|
||||||
|
FileSectionComponent,
|
||||||
FormComponent,
|
FormComponent,
|
||||||
LangSwitchComponent,
|
LangSwitchComponent,
|
||||||
LoadingComponent,
|
LoadingComponent,
|
||||||
|
@@ -2261,6 +2261,28 @@
|
|||||||
"journalvolume.page.volume": "Volume",
|
"journalvolume.page.volume": "Volume",
|
||||||
|
|
||||||
|
|
||||||
|
"iiifsearchable.listelement.badge": "Document Media",
|
||||||
|
|
||||||
|
"iiifsearchable.page.titleprefix": "Document: ",
|
||||||
|
|
||||||
|
"iiifsearchable.page.doi": "Permanent Link: ",
|
||||||
|
|
||||||
|
"iiifsearchable.page.issue": "Issue: ",
|
||||||
|
|
||||||
|
"iiifsearchable.page.description": "Description: ",
|
||||||
|
|
||||||
|
"iiifviewer.fullscreen.notice": "Use full screen for better viewing.",
|
||||||
|
|
||||||
|
"iiif.listelement.badge": "Image Media",
|
||||||
|
|
||||||
|
"iiif.page.titleprefix": "Image: ",
|
||||||
|
|
||||||
|
"iiif.page.doi": "Permanent Link: ",
|
||||||
|
|
||||||
|
"iiif.page.issue": "Issue: ",
|
||||||
|
|
||||||
|
"iiif.page.description": "Description: ",
|
||||||
|
|
||||||
|
|
||||||
"loading.bitstream": "Loading bitstream...",
|
"loading.bitstream": "Loading bitstream...",
|
||||||
|
|
||||||
|
166
src/mirador-viewer/index.js
Normal file
166
src/mirador-viewer/index.js
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import Mirador from 'mirador/dist/es/src/index';
|
||||||
|
import miradorShareDialogPlugin from 'mirador-share-plugin/es/MiradorShareDialog';
|
||||||
|
import miradorSharePlugin from 'mirador-share-plugin/es/miradorSharePlugin';
|
||||||
|
import miradorDownloadPlugin from 'mirador-dl-plugin/es/miradorDownloadPlugin';
|
||||||
|
import miradorDownloadDialog from 'mirador-dl-plugin/es/MiradorDownloadDialog';
|
||||||
|
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const manifest = params.get('manifest');
|
||||||
|
const searchOption = params.get('searchable');
|
||||||
|
const query = params.get('query');
|
||||||
|
const multi = params.get('multi');
|
||||||
|
const notMobile = params.get('notMobile');
|
||||||
|
|
||||||
|
let windowSettings = {};
|
||||||
|
let sidbarPanel = 'info';
|
||||||
|
let defaultView = 'single';
|
||||||
|
let multipleItems = false;
|
||||||
|
let thumbNavigation = 'off';
|
||||||
|
|
||||||
|
windowSettings.manifestId = manifest;
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
if (searchOption) {
|
||||||
|
defaultView = 'book';
|
||||||
|
sidbarPanel = 'search';
|
||||||
|
multipleItems = true;
|
||||||
|
if (notMobile) {
|
||||||
|
thumbNavigation = 'far-right';
|
||||||
|
}
|
||||||
|
if (query !== 'null') {
|
||||||
|
windowSettings.defaultSearchQuery = query;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(multi) {
|
||||||
|
multipleItems = multi;
|
||||||
|
if (notMobile) {
|
||||||
|
thumbNavigation = 'far-right';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(Mirador.viewer(
|
||||||
|
{
|
||||||
|
id: 'mirador',
|
||||||
|
mainMenuSettings: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
thumbnailNavigation: {
|
||||||
|
defaultPosition: thumbNavigation, // Which position for the thumbnail navigation to be be displayed. Other possible values are "far-bottom" or "far-right"
|
||||||
|
displaySettings: true, // Display the settings for this in WindowTopMenu
|
||||||
|
height: 120, // height of entire ThumbnailNavigation area when position is "far-bottom"
|
||||||
|
width: 100, // width of one canvas (doubled for book view) in ThumbnailNavigation area when position is "far-right"
|
||||||
|
},
|
||||||
|
themes: {
|
||||||
|
light: {
|
||||||
|
palette: {
|
||||||
|
type: 'light',
|
||||||
|
primary: {
|
||||||
|
main: '#266883',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: '#b03727',
|
||||||
|
},
|
||||||
|
shades: { // Shades that can be used to offset color areas of the Workspace / Window
|
||||||
|
dark: '#eeeeee',
|
||||||
|
main: '#ffffff',
|
||||||
|
light: '#ffffff',
|
||||||
|
},
|
||||||
|
highlights: {
|
||||||
|
primary: '#ffff00',
|
||||||
|
secondary: '#00BFFF',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
default: { fillStyle: '#00BFFF', globalAlpha: 0.3 },
|
||||||
|
hovered: { fillStyle: '#00FFFF', globalAlpha: 0.3 },
|
||||||
|
selected: { fillStyle: '#ff0900', globalAlpha: 0.3 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
palette: {
|
||||||
|
type: 'dark',
|
||||||
|
primary: {
|
||||||
|
main: '#2790b0',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: '#eeeeee',
|
||||||
|
},
|
||||||
|
highlights: {
|
||||||
|
primary: '#ffff00',
|
||||||
|
secondary: '#00BFFF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
selectedTheme: 'light',
|
||||||
|
data: [manifest],
|
||||||
|
windows: [
|
||||||
|
windowSettings
|
||||||
|
],
|
||||||
|
miradorSharePlugin: {
|
||||||
|
dragAndDropInfoLink: 'https://iiif.io',
|
||||||
|
embedOption: {
|
||||||
|
enabled: true,
|
||||||
|
embedUrlReplacePattern: [
|
||||||
|
/.*\.edu\/(\w+)\/iiif\/manifest/,
|
||||||
|
manifest
|
||||||
|
],
|
||||||
|
syncIframeDimensions: {
|
||||||
|
height: {param: 'maxheight'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shareLink: {
|
||||||
|
enabled: true,
|
||||||
|
manifestIdReplacePattern: [
|
||||||
|
/\/iiif\/manifest/,
|
||||||
|
'',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
miradorDownloadPlugin: {
|
||||||
|
restrictDownloadOnSizeDefinition: false
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
allowClose: false,
|
||||||
|
// sideBarOpenByDefault: false,
|
||||||
|
allowFullscreen: true,
|
||||||
|
allowMaximize: false,
|
||||||
|
defaultView: defaultView,
|
||||||
|
sideBarOpen: notMobile,
|
||||||
|
allowTopMenuButton: true,
|
||||||
|
defaultSidebarPanelWidth: 230,
|
||||||
|
switchCanvasOnSearch: true,
|
||||||
|
views: [
|
||||||
|
{ key: 'single', behaviors: ['individuals'] },
|
||||||
|
{ key: 'book', behaviors: ['paged'] },
|
||||||
|
{ key: 'scroll', behaviors: ['continuous'] },
|
||||||
|
{ key: 'gallery' },
|
||||||
|
],
|
||||||
|
panels: {
|
||||||
|
info: true,
|
||||||
|
attribution: false,
|
||||||
|
canvas: true,
|
||||||
|
search: searchOption,
|
||||||
|
layers: false,
|
||||||
|
},
|
||||||
|
sideBarPanel: sidbarPanel
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
allowNewWindows: false,
|
||||||
|
showZoomControls: true,
|
||||||
|
type: 'mosaic'
|
||||||
|
},
|
||||||
|
workspaceControlPanel: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
miradorShareDialogPlugin,
|
||||||
|
miradorSharePlugin,
|
||||||
|
miradorDownloadDialog,
|
||||||
|
miradorDownloadPlugin
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)(manifest);
|
10
src/mirador-viewer/mirador.html
Normal file
10
src/mirador-viewer/mirador.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Mirador</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="mirador"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
28
webpack/webpack.mirador.config.ts
Normal file
28
webpack/webpack.mirador.config.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: 'production',
|
||||||
|
entry: {
|
||||||
|
mirador: './src/mirador-viewer/index.js'
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, '..' , 'dist/iiif/mirador'),
|
||||||
|
filename: '[name].js'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.html$/i,
|
||||||
|
loader: 'html-loader',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
contentBase: '../dist/iiif/mirador',
|
||||||
|
},
|
||||||
|
plugins: [new HtmlWebpackPlugin({
|
||||||
|
filename: 'index.html',
|
||||||
|
template: './src/mirador-viewer/mirador.html'
|
||||||
|
})]
|
||||||
|
};
|
Reference in New Issue
Block a user