mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-13 21:13:07 +00:00
Merge branch 'master' into rest-embedded
This commit is contained in:
@@ -18,14 +18,16 @@ cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
bundler_args: --retry 5
|
||||
|
||||
before_install:
|
||||
- yarn run global
|
||||
- travis_retry yarn run global
|
||||
|
||||
install:
|
||||
- yarn install
|
||||
- travis_retry yarn install
|
||||
|
||||
before_script:
|
||||
- yarn run build
|
||||
- travis_wait yarn run build
|
||||
- export CHROME_BIN=chromium-browser
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
|
@@ -4,7 +4,12 @@
|
||||
"link.dspace": "DSpace software",
|
||||
"link.duraspace": "DuraSpace"
|
||||
},
|
||||
|
||||
"collection": {
|
||||
"page": {
|
||||
"news": "News",
|
||||
"license": "License"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"page": {
|
||||
"author": "Author",
|
||||
@@ -12,24 +17,31 @@
|
||||
"date": "Date",
|
||||
"uri": "URI",
|
||||
"files": "Files",
|
||||
"collections": "Collections"
|
||||
"collections": "Collections",
|
||||
"filesection": {
|
||||
"download": "Download",
|
||||
"name": "Name:",
|
||||
"format": "Format:",
|
||||
"size": "Size:",
|
||||
"description": "Description:"
|
||||
},
|
||||
"link": {
|
||||
"simple": "Simple item page",
|
||||
"full": "Full item page"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"nav": {
|
||||
"home": "Home"
|
||||
},
|
||||
|
||||
"pagination": {
|
||||
"results-per-page": "Results Per Page",
|
||||
"showing": {
|
||||
"label" : "Now showing items ",
|
||||
"label": "Now showing items ",
|
||||
"detail": "{{ range }} of {{ total }}"
|
||||
}
|
||||
},
|
||||
|
||||
"title": "DSpace",
|
||||
|
||||
"404": {
|
||||
"help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ",
|
||||
"page-not-found": "page not found",
|
||||
@@ -37,7 +49,6 @@
|
||||
"home-page": "Take me to the home page"
|
||||
}
|
||||
},
|
||||
|
||||
"home": {
|
||||
"top-level-communities": {
|
||||
"head": "Communities in DSpace",
|
||||
|
@@ -9,6 +9,7 @@ import { SharedModule } from './shared/shared.module';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { HeaderComponent } from './header/header.component';
|
||||
import { CollectionPageModule } from './collection-page/collection-page.module';
|
||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||
|
||||
|
||||
@@ -22,6 +23,7 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||
SharedModule,
|
||||
HomeModule,
|
||||
ItemPageModule,
|
||||
CollectionPageModule,
|
||||
CoreModule.forRoot(),
|
||||
AppRoutingModule
|
||||
],
|
||||
|
13
src/app/collection-page/collection-page-routing.module.ts
Normal file
13
src/app/collection-page/collection-page-routing.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { CollectionPageComponent } from './collection-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: 'collections/:id', component: CollectionPageComponent }
|
||||
])
|
||||
]
|
||||
})
|
||||
export class CollectionPageRoutingModule { }
|
16
src/app/collection-page/collection-page.component.html
Normal file
16
src/app/collection-page/collection-page.component.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<div class="collection-page" *ngIf="collectionData.hasSucceeded | async">
|
||||
<ds-collection-page-name [name]="(collectionData.payload | async)?.name"></ds-collection-page-name>
|
||||
<ds-collection-page-logo *ngIf="logoData" [logo]="logoData.payload | async"></ds-collection-page-logo>
|
||||
<ds-collection-page-introductory-text
|
||||
[introductoryText]="(collectionData.payload | async)?.introductoryText">
|
||||
</ds-collection-page-introductory-text>
|
||||
<ds-collection-page-news
|
||||
[sidebarText]="(collectionData.payload | async)?.sidebarText">
|
||||
</ds-collection-page-news>
|
||||
<ds-collection-page-copyright
|
||||
[copyrightText]="(collectionData.payload | async)?.copyrightText">
|
||||
</ds-collection-page-copyright>
|
||||
<ds-collection-page-license
|
||||
[license]="(collectionData.payload | async)?.license">
|
||||
</ds-collection-page-license>
|
||||
</div>
|
35
src/app/collection-page/collection-page.component.ts
Normal file
35
src/app/collection-page/collection-page.component.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
import { Collection } from "../core/shared/collection.model";
|
||||
import { Bitstream } from "../core/shared/bitstream.model";
|
||||
import { RemoteData } from "../core/data/remote-data";
|
||||
import { CollectionDataService } from "../core/data/collection-data.service";
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page',
|
||||
styleUrls: ['./collection-page.component.css'],
|
||||
templateUrl: './collection-page.component.html',
|
||||
})
|
||||
export class CollectionPageComponent implements OnInit {
|
||||
collectionData: RemoteData<Collection>;
|
||||
logoData: RemoteData<Bitstream>;
|
||||
|
||||
constructor(
|
||||
private collectionDataService: CollectionDataService,
|
||||
private route: ActivatedRoute
|
||||
) {
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.params.subscribe((params: Params) => {
|
||||
this.collectionData = this.collectionDataService.findById(params['id'])
|
||||
this.collectionData.payload
|
||||
.subscribe(collection => this.logoData = collection.logo);
|
||||
});
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
}
|
||||
}
|
33
src/app/collection-page/collection-page.module.ts
Normal file
33
src/app/collection-page/collection-page.module.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
|
||||
import { CollectionPageComponent } from './collection-page.component';
|
||||
import { FieldWrapperComponent } from './field-wrapper/field-wrapper.component';
|
||||
import { CollectionPageNameComponent } from './name/collection-page-name.component';
|
||||
import { CollectionPageLogoComponent } from './logo/collection-page-logo.component';
|
||||
import { CollectionPageIntroductoryTextComponent } from './introductory-text/collection-page-introductory-text.component';
|
||||
import { CollectionPageNewsComponent } from './news/collection-page-news.component';
|
||||
import { CollectionPageCopyrightComponent } from './copyright/collection-page-copyright.component';
|
||||
import { CollectionPageLicenseComponent } from './license/collection-page-license.component';
|
||||
import { CollectionPageRoutingModule } from './collection-page-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CollectionPageRoutingModule,
|
||||
CommonModule,
|
||||
TranslateModule,
|
||||
],
|
||||
declarations: [
|
||||
CollectionPageComponent,
|
||||
FieldWrapperComponent,
|
||||
CollectionPageNameComponent,
|
||||
CollectionPageLogoComponent,
|
||||
CollectionPageIntroductoryTextComponent,
|
||||
CollectionPageNewsComponent,
|
||||
CollectionPageCopyrightComponent,
|
||||
CollectionPageLicenseComponent,
|
||||
]
|
||||
})
|
||||
export class CollectionPageModule { }
|
@@ -0,0 +1,3 @@
|
||||
<ds-field-wrapper *ngIf="copyrightText" class="collection-page-copyright">
|
||||
<p [innerHtml]="copyrightText"></p>
|
||||
</ds-field-wrapper>
|
@@ -0,0 +1 @@
|
||||
@import '../../../styles/variables.scss';
|
@@ -0,0 +1,11 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page-copyright',
|
||||
styleUrls: ['./collection-page-copyright.component.css'],
|
||||
templateUrl: './collection-page-copyright.component.html',
|
||||
})
|
||||
export class CollectionPageCopyrightComponent {
|
||||
@Input() copyrightText: String;
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
<div class="collection-page-field-wrapper">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
@@ -0,0 +1 @@
|
||||
@import '../../../styles/variables.scss';
|
@@ -0,0 +1,11 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ds-field-wrapper',
|
||||
styleUrls: ['./field-wrapper.component.css'],
|
||||
templateUrl: './field-wrapper.component.html',
|
||||
})
|
||||
export class FieldWrapperComponent {
|
||||
@Input() name: String;
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
<ds-field-wrapper *ngIf="introductoryText" class="collection-page-introductory-text">
|
||||
<p [innerHtml]="introductoryText"></p>
|
||||
</ds-field-wrapper>
|
@@ -0,0 +1 @@
|
||||
@import '../../../styles/variables.scss';
|
@@ -0,0 +1,11 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page-introductory-text',
|
||||
styleUrls: ['./collection-page-introductory-text.component.css'],
|
||||
templateUrl: './collection-page-introductory-text.component.html',
|
||||
})
|
||||
export class CollectionPageIntroductoryTextComponent {
|
||||
@Input() introductoryText: String;
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
<ds-field-wrapper *ngIf="license" class="collection-page-license">
|
||||
<h2>{{ 'collection.page.license' | translate }}</h2>
|
||||
<p>{{ license }}</p>
|
||||
</ds-field-wrapper>
|
@@ -0,0 +1 @@
|
||||
@import '../../../styles/variables.scss';
|
@@ -0,0 +1,11 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page-license',
|
||||
styleUrls: ['./collection-page-license.component.css'],
|
||||
templateUrl: './collection-page-license.component.html',
|
||||
})
|
||||
export class CollectionPageLicenseComponent {
|
||||
@Input() license: String;
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
<ds-field-wrapper *ngIf="logo" class="collection-page-logo">
|
||||
<img [src]="logo.url" class="img-responsive" alt="Collection logo" />
|
||||
</ds-field-wrapper>
|
@@ -0,0 +1 @@
|
||||
@import '../../../styles/variables.scss';
|
@@ -0,0 +1,13 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { Bitstream } from "../../core/shared/bitstream.model";
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page-logo',
|
||||
styleUrls: ['./collection-page-logo.component.css'],
|
||||
templateUrl: './collection-page-logo.component.html',
|
||||
})
|
||||
export class CollectionPageLogoComponent {
|
||||
@Input() logo: Bitstream;
|
||||
}
|
@@ -0,0 +1 @@
|
||||
<h1 *ngIf="name">{{ name }}</h1>
|
@@ -0,0 +1 @@
|
||||
@import '../../../styles/variables.scss';
|
@@ -0,0 +1,11 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page-name',
|
||||
styleUrls: ['./collection-page-name.component.css'],
|
||||
templateUrl: './collection-page-name.component.html',
|
||||
})
|
||||
export class CollectionPageNameComponent {
|
||||
@Input() name: String;
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
<ds-field-wrapper *ngIf="sidebarText" class="collection-page-news">
|
||||
<h2>{{ 'collection.page.news' | translate }}</h2>
|
||||
<p [innerHtml]="sidebarText"></p>
|
||||
</ds-field-wrapper>
|
@@ -0,0 +1 @@
|
||||
@import '../../../styles/variables.scss';
|
@@ -0,0 +1,11 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page-news',
|
||||
styleUrls: ['./collection-page-news.component.css'],
|
||||
templateUrl: './collection-page-news.component.html',
|
||||
})
|
||||
export class CollectionPageNewsComponent {
|
||||
@Input() sidebarText: String;
|
||||
}
|
@@ -135,9 +135,12 @@ export class RemoteDataBuildService {
|
||||
});
|
||||
}, 0);
|
||||
|
||||
links[relationship] = normalized[relationship].map((href: string) => {
|
||||
return this.buildSingle(href, resourceConstructor);
|
||||
let rdArr = [];
|
||||
normalized[relationship].forEach((href: string) => {
|
||||
rdArr.push(this.buildSingle(href, resourceConstructor));
|
||||
});
|
||||
|
||||
links[relationship] = this.aggregate(rdArr);
|
||||
}
|
||||
else {
|
||||
// without the setTimeout, the actions inside requestService.configure
|
||||
@@ -154,4 +157,49 @@ export class RemoteDataBuildService {
|
||||
const domainModel = getMapsTo(normalized.constructor);
|
||||
return Object.assign(new domainModel(), normalized, links);
|
||||
}
|
||||
|
||||
aggregate<T>(input: RemoteData<T>[]): RemoteData<T[]> {
|
||||
const requestPending = Observable.combineLatest(
|
||||
...input.map(rd => rd.isRequestPending),
|
||||
).map((...pendingArray) => pendingArray.every(e => e === true))
|
||||
.distinctUntilChanged();
|
||||
|
||||
const responsePending = Observable.combineLatest(
|
||||
...input.map(rd => rd.isResponsePending),
|
||||
).map((...pendingArray) => pendingArray.every(e => e === true))
|
||||
.distinctUntilChanged();
|
||||
|
||||
const isSuccessFul = Observable.combineLatest(
|
||||
...input.map(rd => rd.hasSucceeded),
|
||||
).map((...successArray) => successArray.every(e => e === true))
|
||||
.distinctUntilChanged();
|
||||
|
||||
const errorMessage = Observable.combineLatest(
|
||||
...input.map(rd => rd.errorMessage),
|
||||
).map((...errors) => errors
|
||||
.map((e, idx) => {
|
||||
if (hasValue(e)) {
|
||||
return `[${idx}]: ${e}`;
|
||||
}
|
||||
})
|
||||
.filter(e => hasValue(e))
|
||||
.join(", ")
|
||||
);
|
||||
|
||||
const payload = <Observable<T[]>> Observable.combineLatest(
|
||||
...input.map(rd => rd.payload)
|
||||
);
|
||||
|
||||
return new RemoteData(
|
||||
// This is an aggregated object, it doesn't necessarily correspond
|
||||
// to a single REST endpoint, so instead of a self link, use the
|
||||
// current time in ms for a somewhat unique id
|
||||
`${new Date().getTime()}`,
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessFul,
|
||||
errorMessage,
|
||||
payload
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -16,13 +16,20 @@ export class NormalizedBitstream extends NormalizedDSpaceObject {
|
||||
/**
|
||||
* The relative path to this Bitstream's file
|
||||
*/
|
||||
@autoserialize
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* The mime type of this Bitstream
|
||||
*/
|
||||
@autoserialize
|
||||
mimetype: string;
|
||||
|
||||
/**
|
||||
* The format of this Bitstream
|
||||
*/
|
||||
format: string;
|
||||
|
||||
/**
|
||||
* The description of this Bitstream
|
||||
*/
|
||||
|
@@ -17,6 +17,8 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
|
||||
/**
|
||||
* The Bitstream that represents the logo of this Collection
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(NormalizedDSOType.NormalizedBitstream)
|
||||
logo: string;
|
||||
|
||||
/**
|
||||
|
@@ -27,7 +27,7 @@ export class Bitstream extends DSpaceObject {
|
||||
/**
|
||||
* An array of Bundles that are direct parents of this Bitstream
|
||||
*/
|
||||
parents: Array<RemoteData<Bundle>>;
|
||||
parents: RemoteData<Bundle[]>;
|
||||
|
||||
/**
|
||||
* The Bundle that owns this Bitstream
|
||||
|
@@ -12,13 +12,13 @@ export class Bundle extends DSpaceObject {
|
||||
/**
|
||||
* An array of Items that are direct parents of this Bundle
|
||||
*/
|
||||
parents: Array<RemoteData<Item>>;
|
||||
parents: RemoteData<Item[]>;
|
||||
|
||||
/**
|
||||
* The Item that owns this Bundle
|
||||
*/
|
||||
owner: Item;
|
||||
|
||||
bitstreams: Array<RemoteData<Bitstream>>
|
||||
bitstreams: RemoteData<Bitstream[]>
|
||||
|
||||
}
|
||||
|
@@ -58,13 +58,13 @@ export class Collection extends DSpaceObject {
|
||||
/**
|
||||
* An array of Collections that are direct parents of this Collection
|
||||
*/
|
||||
parents: Array<RemoteData<Collection>>;
|
||||
parents: RemoteData<Collection[]>;
|
||||
|
||||
/**
|
||||
* The Collection that owns this Collection
|
||||
*/
|
||||
owner: Collection;
|
||||
|
||||
items: Array<RemoteData<Item>>;
|
||||
items: RemoteData<Item[]>;
|
||||
|
||||
}
|
||||
|
@@ -50,13 +50,13 @@ export class Community extends DSpaceObject {
|
||||
/**
|
||||
* An array of Communities that are direct parents of this Community
|
||||
*/
|
||||
parents: Array<RemoteData<DSpaceObject>>;
|
||||
parents: RemoteData<DSpaceObject[]>;
|
||||
|
||||
/**
|
||||
* The Community that owns this Community
|
||||
*/
|
||||
owner: Community;
|
||||
|
||||
collections: Array<RemoteData<Collection>>;
|
||||
collections: RemoteData<Collection[]>;
|
||||
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ export abstract class DSpaceObject implements CacheableObject {
|
||||
/**
|
||||
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
||||
*/
|
||||
parents: Array<RemoteData<DSpaceObject>>;
|
||||
parents: RemoteData<DSpaceObject[]>;
|
||||
|
||||
/**
|
||||
* The DSpaceObject that owns this DSpaceObject
|
||||
|
115
src/app/core/shared/item.model.spec.ts
Normal file
115
src/app/core/shared/item.model.spec.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { Item } from "./item.model";
|
||||
import { Bundle } from "./bundle.model";
|
||||
import { Observable } from "rxjs";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { Bitstream } from "./bitstream.model";
|
||||
|
||||
|
||||
describe('Item', () => {
|
||||
|
||||
|
||||
let item: Item;
|
||||
const thumbnailBundleName = "THUMBNAIL";
|
||||
const originalBundleName = "ORIGINAL";
|
||||
const thumbnailPath = "thumbnail.jpg";
|
||||
const bitstream1Path = "document.pdf";
|
||||
const bitstream2Path = "otherfile.doc";
|
||||
|
||||
const nonExistingBundleName = "c1e568f7-d14e-496b-bdd7-07026998cc00";
|
||||
let remoteBundles;
|
||||
let thumbnailBundle;
|
||||
let originalBundle;
|
||||
|
||||
beforeEach(() => {
|
||||
const thumbnail = {
|
||||
retrieve: thumbnailPath
|
||||
};
|
||||
|
||||
const bitstreams = [{
|
||||
retrieve: bitstream1Path
|
||||
}, {
|
||||
retrieve: bitstream2Path
|
||||
}];
|
||||
|
||||
const remoteDataThumbnail = createRemoteDataObject(thumbnail);
|
||||
const remoteDataFiles = createRemoteDataObject(bitstreams);
|
||||
|
||||
|
||||
// Create Bundles
|
||||
|
||||
const bundles =
|
||||
[
|
||||
{
|
||||
name: thumbnailBundleName,
|
||||
primaryBitstream: remoteDataThumbnail
|
||||
},
|
||||
|
||||
{
|
||||
name: originalBundleName,
|
||||
bitstreams: remoteDataFiles
|
||||
}];
|
||||
|
||||
remoteBundles = createRemoteDataObject(bundles);
|
||||
|
||||
item = Object.assign(new Item(), { bundles: remoteBundles });
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should return the bundle with the given name of this item when the bundle exists', () => {
|
||||
let name: string = thumbnailBundleName;
|
||||
let bundle: Observable<Bundle> = item.getBundle(name);
|
||||
bundle.map(b => expect(b.name).toBe(name));
|
||||
});
|
||||
|
||||
it('should return null when no bundle with this name exists for this item', () => {
|
||||
let name: string = nonExistingBundleName;
|
||||
let bundle: Observable<Bundle> = item.getBundle(name);
|
||||
bundle.map(b => expect(b).toBeUndefined());
|
||||
});
|
||||
|
||||
|
||||
describe("get thumbnail", () => {
|
||||
beforeEach(() => {
|
||||
spyOn(item, 'getBundle').and.returnValue(Observable.of(thumbnailBundle));
|
||||
});
|
||||
|
||||
it('should return the thumbnail (the primaryBitstream in the bundle "THUMBNAIL") of this item', () => {
|
||||
let path: string = thumbnailPath;
|
||||
let bitstream: Observable<Bitstream> = item.getThumbnail();
|
||||
bitstream.map(b => expect(b.retrieve).toBe(path));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("get files", () => {
|
||||
beforeEach(() => {
|
||||
spyOn(item, 'getBundle').and.returnValue(Observable.of(originalBundle));
|
||||
});
|
||||
|
||||
it('should return all files in the ORIGINAL bundle', () => {
|
||||
let paths = [bitstream1Path, bitstream2Path];
|
||||
|
||||
let files: Observable<Bitstream[]> = item.getFiles();
|
||||
let index = 0;
|
||||
files.map(f => expect(f.length).toBe(2));
|
||||
files.subscribe(
|
||||
array => array.forEach(
|
||||
file => {
|
||||
expect(file.retrieve).toBe(paths[index]);
|
||||
index++;
|
||||
}
|
||||
)
|
||||
)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
function createRemoteDataObject(object: Object) {
|
||||
return new RemoteData("", Observable.of(false), Observable.of(false), Observable.of(true), Observable.of(undefined), Observable.of(object));
|
||||
|
||||
}
|
@@ -4,6 +4,7 @@ import { RemoteData } from "../data/remote-data";
|
||||
import { Bundle } from "./bundle.model";
|
||||
import { Bitstream } from "./bitstream.model";
|
||||
import { Observable } from "rxjs";
|
||||
import { hasValue } from "../../shared/empty.util";
|
||||
|
||||
export class Item extends DSpaceObject {
|
||||
|
||||
@@ -30,51 +31,72 @@ export class Item extends DSpaceObject {
|
||||
/**
|
||||
* An array of Collections that are direct parents of this Item
|
||||
*/
|
||||
parents: Array<RemoteData<Collection>>;
|
||||
parents: RemoteData<Collection[]>;
|
||||
|
||||
/**
|
||||
* The Collection that owns this Item
|
||||
*/
|
||||
owner: Collection;
|
||||
|
||||
bundles: Array<RemoteData<Bundle>>;
|
||||
bundles: RemoteData<Bundle[]>;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the thumbnail of this item
|
||||
* @returns {Observable<Bitstream>} the primaryBitstream of the "THUMBNAIL" bundle
|
||||
*/
|
||||
getThumbnail(): Observable<Bitstream> {
|
||||
const bundle: Observable<Bundle> = this.getBundle("THUMBNAIL");
|
||||
return bundle.flatMap(
|
||||
bundle => {
|
||||
if (bundle != null) {
|
||||
return bundle.primaryBitstream.payload;
|
||||
}
|
||||
else {
|
||||
return Observable.of(undefined);
|
||||
}
|
||||
}
|
||||
);
|
||||
return bundle
|
||||
.filter(bundle => hasValue(bundle))
|
||||
.flatMap(bundle => bundle.primaryBitstream.payload)
|
||||
.startWith(undefined);
|
||||
}
|
||||
|
||||
getFiles(): Observable<Array<Observable<Bitstream>>> {
|
||||
const bundle: Observable <Bundle> = this.getBundle("ORIGINAL");
|
||||
return bundle.map(bundle => {
|
||||
if (bundle != null) {
|
||||
return bundle.bitstreams.map(bitstream => bitstream.payload)
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Retrieves the thumbnail for the given original of this item
|
||||
* @returns {Observable<Bitstream>} the primaryBitstream of the "THUMBNAIL" bundle
|
||||
*/
|
||||
getThumbnailForOriginal(original: Bitstream): Observable<Bitstream> {
|
||||
const bundle: Observable<Bundle> = this.getBundle("THUMBNAIL");
|
||||
return bundle
|
||||
.filter(bundle => hasValue(bundle))
|
||||
.flatMap(bundle => bundle
|
||||
.bitstreams.payload.map(files => files
|
||||
.find(thumbnail => thumbnail
|
||||
.name.startsWith(original.name)
|
||||
)
|
||||
)
|
||||
)
|
||||
.startWith(undefined);;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all files that should be displayed on the item page of this item
|
||||
* @returns {Observable<Array<Observable<Bitstream>>>} an array of all Bitstreams in the "ORIGINAL" bundle
|
||||
*/
|
||||
getFiles(name: String = "ORIGINAL"): Observable<Bitstream[]> {
|
||||
const bundle: Observable <Bundle> = this.getBundle(name);
|
||||
return bundle
|
||||
.filter(bundle => hasValue(bundle))
|
||||
.flatMap(bundle => bundle.bitstreams.payload)
|
||||
.startWith([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the bundle of this item by its name
|
||||
* @param name The name of the Bundle that should be returned
|
||||
* @returns {Observable<Bundle>} the Bundle that belongs to this item with the given name
|
||||
*/
|
||||
getBundle(name: String): Observable<Bundle> {
|
||||
return Observable.combineLatest(
|
||||
...this.bundles.map(b => b.payload),
|
||||
(...bundles: Array<Bundle>) => bundles)
|
||||
return this.bundles.payload
|
||||
.filter(bundles => hasValue(bundles))
|
||||
.map(bundles => {
|
||||
return bundles.find((bundle: Bundle) => {
|
||||
return bundle.name === name
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getCollections(): Array<Observable<Collection>> {
|
||||
return this.parents.map(collection => collection.payload.map(parent => parent));
|
||||
})
|
||||
.startWith(undefined);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +0,0 @@
|
||||
<ds-metadata-field-wrapper [label]="label | translate">
|
||||
<div class="collections">
|
||||
<a *ngFor="let collection of collections; let last=last;" [href]="(collection | async)?.self">
|
||||
<span>{{(collection | async)?.name}}</span><span *ngIf="!last" [innerHTML]="separator"></span>
|
||||
</a>
|
||||
</div>
|
||||
</ds-metadata-field-wrapper>
|
@@ -0,0 +1,7 @@
|
||||
<ds-metadata-field-wrapper [label]="label | translate">
|
||||
<div class="collections">
|
||||
<a *ngFor="let collection of (collections | async); let last=last;" [href]="collection?.self">
|
||||
<span>{{collection?.name}}</span><span *ngIf="!last" [innerHTML]="separator"></span>
|
||||
</a>
|
||||
</div>
|
||||
</ds-metadata-field-wrapper>
|
@@ -1,7 +1,12 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Collection } from "../../core/shared/collection.model";
|
||||
import { Collection } from "../../../core/shared/collection.model";
|
||||
import { Observable } from "rxjs";
|
||||
import { Item } from "../../core/shared/item.model";
|
||||
import { Item } from "../../../core/shared/item.model";
|
||||
|
||||
/**
|
||||
* This component renders the parent collections section of the item
|
||||
* inside a 'ds-metadata-field-wrapper' component.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-page-collections',
|
||||
@@ -15,7 +20,7 @@ export class CollectionsComponent implements OnInit {
|
||||
|
||||
separator: string = "<br/>"
|
||||
|
||||
collections: Array<Observable<Collection>>;
|
||||
collections: Observable<Collection[]>;
|
||||
|
||||
constructor() {
|
||||
this.universalInit();
|
||||
@@ -26,7 +31,7 @@ export class CollectionsComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.collections = this.item.getCollections();
|
||||
this.collections = this.item.parents.payload;
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../../styles/variables.scss';
|
||||
:host {
|
||||
.simple-view-element {
|
||||
margin-bottom: 15px;
|
@@ -1,5 +1,10 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
/**
|
||||
* This component renders any content inside this wrapper.
|
||||
* The wrapper prints a label before the content (if available)
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-metadata-field-wrapper',
|
||||
styleUrls: ['./metadata-field-wrapper.component.css'],
|
@@ -0,0 +1 @@
|
||||
@import '../../../../styles/variables.scss';
|
@@ -1,6 +1,14 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { MetadataValuesComponent } from "../metadata-values/metadata-values.component";
|
||||
|
||||
/**
|
||||
* This component renders the configured 'values' into the ds-metadata-field-wrapper component as a link.
|
||||
* It puts the given 'separator' between each two values
|
||||
* and creates an 'a' tag for each value,
|
||||
* using the 'linktext' as it's value (if it exists)
|
||||
* and using the values as the 'href' attribute (and as value of the tag when no 'linktext' is defined)
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-metadata-uri-values',
|
||||
styleUrls: ['./metadata-uri-values.component.css'],
|
@@ -0,0 +1 @@
|
||||
@import '../../../../styles/variables.scss';
|
@@ -1,5 +1,10 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
/**
|
||||
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
|
||||
* It puts the given 'separator' between each two values.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-metadata-values',
|
||||
styleUrls: ['./metadata-values.component.css'],
|
@@ -0,0 +1,29 @@
|
||||
<ds-metadata-field-wrapper [label]="label | translate">
|
||||
<div class="file-section row" *ngFor="let file of (files | async); let last=last;">
|
||||
<div class="col-3">
|
||||
<ds-thumbnail [thumbnail]="thumbnails.get(file.id) | async"></ds-thumbnail>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<dl class="row">
|
||||
<dt class="col-md-4">{{"item.page.filesection.name" | translate}}</dt>
|
||||
<dd class="col-md-8">{{file.name}}</dd>
|
||||
|
||||
<dt class="col-md-4">{{"item.page.filesection.size" | translate}}</dt>
|
||||
<dd class="col-md-8">{{(file.size) | dsFileSize }}</dd>
|
||||
|
||||
|
||||
<dt class="col-md-4">{{"item.page.filesection.format" | translate}}</dt>
|
||||
<dd class="col-md-8">{{(file.mimetype)}}</dd>
|
||||
|
||||
|
||||
<dt class="col-md-4">{{"item.page.filesection.description" | translate}}</dt>
|
||||
<dd class="col-md-8">{{file.findMetadata("dc.description")}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<a [href]="file.retrieve">
|
||||
{{"item.page.filesection.download" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</ds-metadata-field-wrapper>
|
@@ -0,0 +1,7 @@
|
||||
@import '../../../../../styles/variables.scss';
|
||||
@import '../../../../../../node_modules/bootstrap/scss/_variables.scss';
|
||||
@media screen and (min-width: map-get($grid-breakpoints, md)) {
|
||||
dt {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Bitstream } from "../../../../core/shared/bitstream.model";
|
||||
import { Item } from "../../../../core/shared/item.model";
|
||||
import { Observable } from "rxjs";
|
||||
import { FileSectionComponent } from "../../../simple/field-components/file-section/file-section.component";
|
||||
import { hasValue } from "../../../../shared/empty.util";
|
||||
|
||||
/**
|
||||
* This component renders the file section of the item
|
||||
* inside a 'ds-metadata-field-wrapper' component.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-page-full-file-section',
|
||||
styleUrls: ['./full-file-section.component.css'],
|
||||
templateUrl: './full-file-section.component.html'
|
||||
})
|
||||
export class FullFileSectionComponent extends FileSectionComponent implements OnInit {
|
||||
|
||||
@Input() item: Item;
|
||||
|
||||
label : string;
|
||||
|
||||
files: Observable<Bitstream[]>;
|
||||
|
||||
|
||||
thumbnails: Map<string, Observable<Bitstream>> = new Map();
|
||||
|
||||
|
||||
universalInit() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
const originals = this.item.getFiles("ORIGINAL");
|
||||
const licenses = this.item.getFiles("LICENSE");
|
||||
this.files = Observable.combineLatest(originals, licenses, (originals, licenses) => [...originals, ...licenses]);
|
||||
this.files.subscribe(
|
||||
files =>
|
||||
files.forEach(
|
||||
original => {
|
||||
const thumbnail: Observable<Bitstream> = this.item.getThumbnailForOriginal(original);
|
||||
this.thumbnails.set(original.id, thumbnail);
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
25
src/app/item-page/full/full-item-page.component.html
Normal file
25
src/app/item-page/full/full-item-page.component.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="item-page" *ngIf="item.hasSucceeded | async">
|
||||
<ds-item-page-title-field [item]="item.payload | async"></ds-item-page-title-field>
|
||||
|
||||
<div class="simple-view-link">
|
||||
<a class="btn btn-secondary col-4" [routerLink]="['/items/' + (item.payload | async)?.id]">
|
||||
{{"item.page.link.simple" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<table class="table table-responsive table-striped">
|
||||
<tbody>
|
||||
<tr *ngFor="let metadatum of (metadata | async)">
|
||||
<td>{{metadatum.key}}</td>
|
||||
<td>{{metadatum.value}}</td>
|
||||
<td>{{metadatum.language}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ds-item-page-full-file-section [item]="item.payload | async"></ds-item-page-full-file-section>
|
||||
|
||||
<ds-item-page-collections [item]="item.payload | async"></ds-item-page-collections>
|
||||
|
||||
|
||||
</div>
|
7
src/app/item-page/full/full-item-page.component.scss
Normal file
7
src/app/item-page/full/full-item-page.component.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
:host {
|
||||
div.simple-view-link {
|
||||
text-align: center;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
45
src/app/item-page/full/full-item-page.component.ts
Normal file
45
src/app/item-page/full/full-item-page.component.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from "rxjs";
|
||||
import { ItemPageComponent } from "../simple/item-page.component";
|
||||
import { Metadatum } from "../../core/shared/metadatum.model";
|
||||
import { ItemDataService } from "../../core/data/item-data.service";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { RemoteData } from "../../core/data/remote-data";
|
||||
import { Item } from "../../core/shared/item.model";
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
* The route parameter 'id' is used to request the item it represents.
|
||||
* All fields of the item that should be displayed, are defined in its template.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-full-item-page',
|
||||
styleUrls: ['./full-item-page.component.css'],
|
||||
templateUrl: './full-item-page.component.html',
|
||||
})
|
||||
export class FullItemPageComponent extends ItemPageComponent implements OnInit {
|
||||
|
||||
item: RemoteData<Item>;
|
||||
|
||||
metadata: Observable<Array<Metadatum>>;
|
||||
|
||||
constructor(route: ActivatedRoute, items: ItemDataService) {
|
||||
super(route, items);
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
|
||||
}
|
||||
|
||||
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
initialize(params) {
|
||||
super.initialize(params);
|
||||
this.metadata = this.item.payload.map(i => i.metadata);
|
||||
}
|
||||
|
||||
}
|
@@ -1,12 +1,14 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { ItemPageComponent } from './item-page.component';
|
||||
import { ItemPageComponent } from './simple/item-page.component';
|
||||
import { FullItemPageComponent } from './full/full-item-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: 'items/:id', pathMatch: 'full', component: ItemPageComponent },
|
||||
{ path: 'items/:id/full', component: FullItemPageComponent },
|
||||
])
|
||||
]
|
||||
})
|
||||
|
@@ -1,23 +1,26 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ItemPageComponent } from './item-page.component';
|
||||
import { ItemPageComponent } from './simple/item-page.component';
|
||||
import { ItemPageRoutingModule } from './item-page-routing.module';
|
||||
import { MetadataValuesComponent } from './metadata-values/metadata-values.component';
|
||||
import { MetadataUriValuesComponent } from './metadata-uri-values/metadata-uri-values.component';
|
||||
import { MetadataFieldWrapperComponent } from './metadata-field-wrapper/metadata-field-wrapper.component';
|
||||
import { ItemPageAuthorFieldComponent } from './specific-field/author/item-page-author-field.component';
|
||||
import { ItemPageDateFieldComponent } from './specific-field/date/item-page-date-field.component';
|
||||
import { ItemPageAbstractFieldComponent } from './specific-field/abstract/item-page-abstract-field.component';
|
||||
import { ItemPageUriFieldComponent } from './specific-field/uri/item-page-uri-field.component';
|
||||
import { ItemPageTitleFieldComponent } from './specific-field/title/item-page-title-field.component';
|
||||
import { ItemPageSpecificFieldComponent } from './specific-field/item-page-specific-field.component';
|
||||
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
|
||||
import { MetadataUriValuesComponent } from './field-components/metadata-uri-values/metadata-uri-values.component';
|
||||
import { MetadataFieldWrapperComponent } from './field-components/metadata-field-wrapper/metadata-field-wrapper.component';
|
||||
import { ItemPageAuthorFieldComponent } from './simple/field-components/specific-field/author/item-page-author-field.component';
|
||||
import { ItemPageDateFieldComponent } from './simple/field-components/specific-field/date/item-page-date-field.component';
|
||||
import { ItemPageAbstractFieldComponent } from './simple/field-components/specific-field/abstract/item-page-abstract-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 { ItemPageSpecificFieldComponent } from './simple/field-components/specific-field/item-page-specific-field.component';
|
||||
import { SharedModule } from './../shared/shared.module';
|
||||
import { FileSectionComponent } from "./file-section/file-section.component";
|
||||
import { CollectionsComponent } from "./collections/collections.component";
|
||||
import { FileSectionComponent } from "./simple/field-components/file-section/file-section.component";
|
||||
import { CollectionsComponent } from "./field-components/collections/collections.component";
|
||||
import { FullItemPageComponent } from "./full/full-item-page.component";
|
||||
import { FullFileSectionComponent } from "./full/field-components/file-section/full-file-section.component";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ItemPageComponent,
|
||||
FullItemPageComponent,
|
||||
MetadataValuesComponent,
|
||||
MetadataUriValuesComponent,
|
||||
MetadataFieldWrapperComponent,
|
||||
@@ -28,7 +31,8 @@ import { CollectionsComponent } from "./collections/collections.component";
|
||||
ItemPageTitleFieldComponent,
|
||||
ItemPageSpecificFieldComponent,
|
||||
FileSectionComponent,
|
||||
CollectionsComponent
|
||||
CollectionsComponent,
|
||||
FullFileSectionComponent
|
||||
],
|
||||
imports: [
|
||||
ItemPageRoutingModule,
|
||||
|
@@ -1 +0,0 @@
|
||||
@import '../../../styles/variables.scss';
|
@@ -1,8 +1,8 @@
|
||||
<ds-metadata-field-wrapper [label]="label | translate">
|
||||
<div class="file-section">
|
||||
<a *ngFor="let file of (files | async); let last=last;" [href]="(file | async)?.retrieve">
|
||||
<span>{{(file | async)?.name}}</span>
|
||||
<span>({{((file | async)?.size) | dsFileSize }})</span>
|
||||
<a *ngFor="let file of (files | async); let last=last;" [href]="file?.retrieve">
|
||||
<span>{{file?.name}}</span>
|
||||
<span>({{(file?.size) | dsFileSize }})</span>
|
||||
<span *ngIf="!last" innerHTML="{{separator}}"></span>
|
||||
</a>
|
||||
</div>
|
@@ -1,8 +1,13 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Bitstream } from "../../core/shared/bitstream.model";
|
||||
import { Item } from "../../core/shared/item.model";
|
||||
import { Bitstream } from "../../../../core/shared/bitstream.model";
|
||||
import { Item } from "../../../../core/shared/item.model";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
/**
|
||||
* This component renders the file section of the item
|
||||
* inside a 'ds-metadata-field-wrapper' component.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-page-file-section',
|
||||
templateUrl: './file-section.component.html'
|
||||
@@ -13,21 +18,23 @@ export class FileSectionComponent implements OnInit {
|
||||
|
||||
label : string = "item.page.files";
|
||||
|
||||
separator: string = "<br/>"
|
||||
separator: string = "<br/>";
|
||||
|
||||
files: Observable<Array<Observable<Bitstream>>>;
|
||||
files: Observable<Bitstream[]>;
|
||||
|
||||
constructor() {
|
||||
this.universalInit();
|
||||
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
this.files = this.item.getFiles();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Item } from "../../../core/shared/item.model";
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from "../../../../../core/shared/item.model";
|
||||
import { ItemPageSpecificFieldComponent } from "../item-page-specific-field.component";
|
||||
|
||||
@Component({
|
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Item } from "../../../core/shared/item.model";
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from "../../../../../core/shared/item.model";
|
||||
import { ItemPageSpecificFieldComponent } from "../item-page-specific-field.component";
|
||||
|
||||
@Component({
|
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Item } from "../../../core/shared/item.model";
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from "../../../../../core/shared/item.model";
|
||||
import { ItemPageSpecificFieldComponent } from "../item-page-specific-field.component";
|
||||
|
||||
@Component({
|
@@ -0,0 +1,40 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from "../../../../core/shared/item.model";
|
||||
|
||||
/**
|
||||
* This component can be used to represent metadata on a simple item page.
|
||||
* It expects one input parameter of type Item to which the metadata belongs.
|
||||
* This class can be extended to print certain metadata.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
templateUrl: './item-page-specific-field.component.html'
|
||||
})
|
||||
export class ItemPageSpecificFieldComponent {
|
||||
|
||||
@Input() item: Item;
|
||||
|
||||
/**
|
||||
* Fields (schema.element.qualifier) used to render their values.
|
||||
*/
|
||||
fields : string[];
|
||||
|
||||
/**
|
||||
* Label i18n key for the rendered metadata
|
||||
*/
|
||||
label : string;
|
||||
|
||||
/**
|
||||
* Separator string between multiple values of the metadata fields defined
|
||||
* @type {string}
|
||||
*/
|
||||
separator : string = "<br/>";
|
||||
|
||||
constructor() {
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Item } from "../../../core/shared/item.model";
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from "../../../../../core/shared/item.model";
|
||||
import { ItemPageSpecificFieldComponent } from "../item-page-specific-field.component";
|
||||
|
||||
@Component({
|
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Item } from "../../../core/shared/item.model";
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from "../../../../../core/shared/item.model";
|
||||
import { ItemPageSpecificFieldComponent } from "../item-page-specific-field.component";
|
||||
|
||||
@Component({
|
@@ -8,12 +8,19 @@
|
||||
<ds-item-page-file-section [item]="item.payload | async"></ds-item-page-file-section>
|
||||
<ds-item-page-date-field [item]="item.payload | async"></ds-item-page-date-field>
|
||||
<ds-item-page-author-field [item]="item.payload | async"></ds-item-page-author-field>
|
||||
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<ds-item-page-abstract-field
|
||||
[item]="item.payload | async"></ds-item-page-abstract-field>
|
||||
<ds-item-page-uri-field [item]="item.payload | async"></ds-item-page-uri-field>
|
||||
<ds-item-page-collections [item]="item.payload | async"></ds-item-page-collections>
|
||||
<div>
|
||||
<a class="btn btn-secondary" [routerLink]="['/items/' + (item.payload | async)?.id + '/full']">
|
||||
{{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
@@ -1,10 +1,16 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Item } from "../core/shared/item.model";
|
||||
import { ItemDataService } from "../core/data/item-data.service";
|
||||
import { RemoteData } from "../core/data/remote-data";
|
||||
import { Item } from "../../core/shared/item.model";
|
||||
import { ItemDataService } from "../../core/data/item-data.service";
|
||||
import { RemoteData } from "../../core/data/remote-data";
|
||||
import { Observable } from "rxjs";
|
||||
import { Bitstream } from "../core/shared/bitstream.model";
|
||||
import { Bitstream } from "../../core/shared/bitstream.model";
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
* The route parameter 'id' is used to request the item it represents.
|
||||
* All fields of the item that should be displayed, are defined in its template.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-page',
|
||||
@@ -31,11 +37,16 @@ export class ItemPageComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.sub = this.route.params.subscribe(params => {
|
||||
this.id = +params['id'];
|
||||
this.item = this.items.findById(params['id']);
|
||||
this.thumbnail = this.item.payload.flatMap(i => i.getThumbnail());
|
||||
this.initialize(params);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
initialize(params) {
|
||||
this.id = +params['id'];
|
||||
this.item = this.items.findById(params['id']);
|
||||
this.thumbnail = this.item.payload.flatMap(i => i.getThumbnail());
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Item } from "../../core/shared/item.model";
|
||||
|
||||
@Component({
|
||||
templateUrl: './item-page-specific-field.component.html'
|
||||
})
|
||||
export class ItemPageSpecificFieldComponent {
|
||||
|
||||
@Input() item: Item;
|
||||
|
||||
fields : string[];
|
||||
|
||||
label : string;
|
||||
|
||||
separator : string = "<br/>";
|
||||
|
||||
constructor() {
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
|
||||
}
|
||||
}
|
@@ -9,6 +9,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
||||
* {{ 1024 | fileSize}}
|
||||
* formats to: 1 KB
|
||||
*/
|
||||
|
||||
@Pipe({name: 'dsFileSize'})
|
||||
export class FileSizePipe implements PipeTransform {
|
||||
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
|
||||
/**
|
||||
* This pipe explicitly escapes the sanitization of a URL,
|
||||
* only use this when you are sure the URL is indeed safe
|
||||
*/
|
||||
|
||||
@Pipe({name: 'dsSafeUrl'})
|
||||
export class SafeUrlPipe implements PipeTransform {
|
||||
constructor(private domSanitizer: DomSanitizer) {}
|
||||
|
49
src/app/thumbnail/thumbnail.component.spec.ts
Normal file
49
src/app/thumbnail/thumbnail.component.spec.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { ThumbnailComponent } from "./thumbnail.component";
|
||||
import { Bitstream } from "../core/shared/bitstream.model";
|
||||
import { SafeUrlPipe } from "../shared/utils/safe-url-pipe";
|
||||
|
||||
|
||||
describe('ThumbnailComponent', () => {
|
||||
let comp: ThumbnailComponent;
|
||||
let fixture: ComponentFixture<ThumbnailComponent>;
|
||||
let de: DebugElement;
|
||||
let el: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ThumbnailComponent, SafeUrlPipe]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
}));
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ThumbnailComponent);
|
||||
|
||||
comp = fixture.componentInstance; // BannerComponent test instance
|
||||
de = fixture.debugElement.query(By.css('div.thumbnail'));
|
||||
el = de.nativeElement;
|
||||
});
|
||||
|
||||
|
||||
it('should display image', () => {
|
||||
comp.thumbnail = new Bitstream();
|
||||
comp.thumbnail.retrieve = "test.url";
|
||||
fixture.detectChanges();
|
||||
let image : HTMLElement = de.query(By.css('img')).nativeElement;
|
||||
expect(image.getAttribute("src")).toBe(comp.thumbnail.retrieve);
|
||||
});
|
||||
|
||||
it('should display placeholder', () => {
|
||||
fixture.detectChanges();
|
||||
let image : HTMLElement = de.query(By.css('img')).nativeElement;
|
||||
expect(image.getAttribute("src")).toBe(comp.holderSource);
|
||||
});
|
||||
|
||||
|
||||
});
|
@@ -1,6 +1,12 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Bitstream } from "../core/shared/bitstream.model";
|
||||
|
||||
/**
|
||||
* This component renders a given Bitstream as a thumbnail.
|
||||
* One input parameter of type Bitstream is expected.
|
||||
* If no Bitstream is provided, a holderjs image will be rendered instead.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-thumbnail',
|
||||
styleUrls: ['./thumbnail.component.css'],
|
||||
|
@@ -43,6 +43,40 @@ export const BITSTREAMS = {
|
||||
],
|
||||
"format": "JPEG",
|
||||
"mimetype": "image/jpeg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/bitstreams/8934" },
|
||||
"bundle": { "href": "/bundles/99f78e5e-3677-43b0-aaef-cddaa1a49092" },
|
||||
"retrieve": { "href": "/rest/bitstreams/ba7d24f2-8fc7-4b8e-b7b6-6c32be1c12a6/retrieve" }
|
||||
},
|
||||
"id": "8934",
|
||||
"uuid": "ba7d24f2-8fc7-4b8e-b7b6-6c32be1c12a6",
|
||||
"name": "license.txt",
|
||||
"size": 41183,
|
||||
"checksum": {
|
||||
"value": "8ad416e8a39e645020e13e06f1427814",
|
||||
"algorithm": "MD5"
|
||||
},
|
||||
"metadata": [
|
||||
{ "key": "dc.title", "value": "license.txt", "language": null },
|
||||
{ "key": "dc.description", "value": "License", "language": "en" }
|
||||
],
|
||||
"format": "Text",
|
||||
"mimetype": "text/plain"
|
||||
},
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/bitstreams/4688" },
|
||||
},
|
||||
"id": "4688",
|
||||
"uuid": "1bb1be24-c934-41e3-a0fb-ca7a71ab0e71",
|
||||
"type": "bitstream",
|
||||
"name": "collection-5179-logo.png",
|
||||
"size": 299832,
|
||||
"url": "/bitstreams/1bb1be24-c934-41e3-a0fb-ca7a71ab0e71/retrieve",
|
||||
"format": "PNG",
|
||||
"mimetype": "image/png"
|
||||
},
|
||||
]
|
||||
};
|
||||
|
@@ -37,6 +37,24 @@ export const BUNDLES = {
|
||||
"metadata": [
|
||||
{ "key": "dc.title", "value": "THUMBNAIL", "language": "en" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/bundles/8475" },
|
||||
"items": [
|
||||
{ "href": "/items/8871" }
|
||||
],
|
||||
"bitstreams": [
|
||||
{ "href": "/bitstreams/8934" },
|
||||
],
|
||||
"primaryBitstream": { "href": "/bitstreams/8934" }
|
||||
},
|
||||
"id": "8475",
|
||||
"uuid": "99f78e5e-3677-43b0-aaef-cddaa1a49092",
|
||||
"name": "LICENSE",
|
||||
"metadata": [
|
||||
{ "key": "dc.title", "value": "LICENSE", "language": "en" }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@@ -6,7 +6,8 @@ export const COLLECTIONS = {
|
||||
"items": [
|
||||
{ "href": "/items/8871" },
|
||||
{ "href": "/items/9978" }
|
||||
]
|
||||
],
|
||||
"logo": { "href": "/bitstreams/4688" }
|
||||
},
|
||||
"id": "5179",
|
||||
"uuid": "9e32a2e2-6b91-4236-a361-995ccdc14c60",
|
||||
|
@@ -17,9 +17,12 @@ export const ITEMS = {
|
||||
{
|
||||
"href": "/bundles/2355"
|
||||
},
|
||||
// {
|
||||
// "href": "/bundles/5687"
|
||||
// }
|
||||
{
|
||||
"href": "/bundles/5687"
|
||||
},
|
||||
{
|
||||
"href": "/bundles/8475"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "8871",
|
||||
@@ -91,87 +94,8 @@ export const ITEMS = {
|
||||
"value": "(not specified)",
|
||||
"language": "en"
|
||||
}
|
||||
],
|
||||
"_embedded": {
|
||||
"parents": [
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/collections/5179" },
|
||||
"items": [
|
||||
{ "href": "/items/8871" },
|
||||
{ "href": "/items/9978" }
|
||||
]
|
||||
},
|
||||
"id": "5179",
|
||||
"uuid": "9e32a2e2-6b91-4236-a361-995ccdc14c60",
|
||||
"type": "collection",
|
||||
"name": "A Test Collection",
|
||||
"handle": "123456789/5179",
|
||||
},
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/collections/6547" },
|
||||
"items": [
|
||||
{ "href": "/items/8871" },
|
||||
{ "href": "/items/9978" }
|
||||
]
|
||||
},
|
||||
"id": "6547",
|
||||
"uuid": "598ce822-c357-46f3-ab70-63724d02d6ad",
|
||||
"type": "collection",
|
||||
"name": "Another Test Collection",
|
||||
"handle": "123456789/6547",
|
||||
}
|
||||
],
|
||||
"bundles": [
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/bundles/2355" },
|
||||
"items": [
|
||||
{ "href": "/items/8871" }
|
||||
],
|
||||
"bitstreams": [
|
||||
{ "href": "/bitstreams/3678" },
|
||||
],
|
||||
"primaryBitstream": { "href": "/bitstreams/3678" }
|
||||
},
|
||||
"id": "2355",
|
||||
"uuid": "35e0606d-5e18-4f9c-aa61-74fc751cc3f9",
|
||||
"type": "bundle",
|
||||
"name": "ORIGINAL",
|
||||
"metadata": [
|
||||
{ "key": "dc.title", "value": "ORIGINAL", "language": "en" }
|
||||
],
|
||||
"_embedded": {
|
||||
"bitstreams": [
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/bitstreams/3678" },
|
||||
"bundle": { "href": "/bundles/35e0606d-5e18-4f9c-aa61-74fc751cc3f9" },
|
||||
"retrieve": { "href": "/bitstreams/43c57c2b-206f-4645-8c8f-5f10c84b09fa/retrieve" }
|
||||
},
|
||||
"id": "3678",
|
||||
"uuid": "43c57c2b-206f-4645-8c8f-5f10c84b09fa",
|
||||
"type": "bitstream",
|
||||
"name": "do_open_access_CRL.pdf",
|
||||
"size": 636626,
|
||||
"checksum": {
|
||||
"value": "063dfbbbac873aa3fca479b878eccff3",
|
||||
"algorithm": "MD5"
|
||||
},
|
||||
"metadata": [
|
||||
{ "key": "dc.title", "value": "do_open_access_CRL.pdf", "language": null },
|
||||
{ "key": "dc.description", "value": "Conference Paper", "language": "en" }
|
||||
],
|
||||
"format": "Adobe PDF",
|
||||
"mimetype": "application/pdf"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"_links": {
|
||||
"self": {
|
||||
@@ -189,9 +113,9 @@ export const ITEMS = {
|
||||
{
|
||||
"href": "/bundles/2355"
|
||||
},
|
||||
// {
|
||||
// "href": "/bundles/5687"
|
||||
// }
|
||||
{
|
||||
"href": "/bundles/5687"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "9978",
|
||||
@@ -243,39 +167,7 @@ export const ITEMS = {
|
||||
"value": "(not specified)",
|
||||
"language": "en"
|
||||
}
|
||||
],
|
||||
"_embedded": {
|
||||
"parents": [
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/collections/5179" },
|
||||
"items": [
|
||||
{ "href": "/items/8871" },
|
||||
{ "href": "/items/9978" }
|
||||
]
|
||||
},
|
||||
"id": "5179",
|
||||
"uuid": "9e32a2e2-6b91-4236-a361-995ccdc14c60",
|
||||
"type": "collection",
|
||||
"name": "A Test Collection",
|
||||
"handle": "123456789/5179",
|
||||
},
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/collections/6547" },
|
||||
"items": [
|
||||
{ "href": "/items/8871" },
|
||||
{ "href": "/items/9978" }
|
||||
]
|
||||
},
|
||||
"id": "6547",
|
||||
"uuid": "598ce822-c357-46f3-ab70-63724d02d6ad",
|
||||
"type": "collection",
|
||||
"name": "Another Test Collection",
|
||||
"handle": "123456789/6547",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@@ -10,5 +10,5 @@
|
||||
* ];
|
||||
**/
|
||||
export const routes: string[] = [
|
||||
'home', 'items/:id' , '**'
|
||||
'home', 'items/:id' , 'collections/:id', '**'
|
||||
];
|
||||
|
@@ -82,9 +82,9 @@ function ngApp(req, res) {
|
||||
function onHandleError(parentZoneDelegate, currentZone, targetZone, error) {
|
||||
console.warn('Error in SSR, serving for direct CSR');
|
||||
res.sendFile('index.html', { root: './src' });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EnvConfig.universal.preboot) {
|
||||
Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => {
|
||||
res.render('index', {
|
||||
req,
|
||||
@@ -97,7 +97,10 @@ function ngApp(req, res) {
|
||||
originUrl: EnvConfig.ui.baseUrl
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
res.sendFile('index.html', { root: './src' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
22
yarn.lock
22
yarn.lock
@@ -301,14 +301,10 @@ alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
||||
|
||||
amdefine@1.0.0:
|
||||
amdefine@1.0.0, amdefine@>=0.0.4:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.0.tgz#fd17474700cb5cc9c2b709f0be9d23ce3c198c33"
|
||||
|
||||
amdefine@>=0.0.4:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
|
||||
|
||||
angular2-express-engine@2.1.0-rc.1:
|
||||
version "2.1.0-rc.1"
|
||||
resolved "https://registry.yarnpkg.com/angular2-express-engine/-/angular2-express-engine-2.1.0-rc.1.tgz#79c8e481cde7ff1253b373cbf98de7c9fab4f215"
|
||||
@@ -3521,11 +3517,7 @@ lowercase-keys@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
|
||||
|
||||
lru-cache@2:
|
||||
version "2.7.3"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
|
||||
|
||||
lru-cache@2.2.x:
|
||||
lru-cache@2, lru-cache@2.2.x:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
|
||||
|
||||
@@ -4659,14 +4651,10 @@ punycode@^1.2.4, punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
|
||||
q@1.4.1:
|
||||
q@1.4.1, q@^1.1.2, q@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
|
||||
|
||||
q@^1.1.2, q@^1.4.1:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
|
||||
|
||||
qjobs@^1.1.4:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73"
|
||||
@@ -5063,13 +5051,13 @@ right-align@^0.1.1:
|
||||
dependencies:
|
||||
align-text "^0.1.1"
|
||||
|
||||
rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.6.0, rimraf@^2.6.1:
|
||||
rimraf@2, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.6.0, rimraf@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
|
||||
dependencies:
|
||||
glob "^7.0.5"
|
||||
|
||||
rimraf@2.5.4, rimraf@^2.4.4:
|
||||
rimraf@2.5.4:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04"
|
||||
dependencies:
|
||||
|
Reference in New Issue
Block a user