Merge branch 'master' into rest-embedded

This commit is contained in:
Art Lowel
2017-06-09 18:09:29 +02:00
86 changed files with 923 additions and 277 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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
],

View 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 { }

View 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>

View 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() {
}
}

View 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 { }

View File

@@ -0,0 +1,3 @@
<ds-field-wrapper *ngIf="copyrightText" class="collection-page-copyright">
<p [innerHtml]="copyrightText"></p>
</ds-field-wrapper>

View File

@@ -0,0 +1 @@
@import '../../../styles/variables.scss';

View File

@@ -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;
}

View File

@@ -0,0 +1,3 @@
<div class="collection-page-field-wrapper">
<ng-content></ng-content>
</div>

View File

@@ -0,0 +1 @@
@import '../../../styles/variables.scss';

View File

@@ -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;
}

View File

@@ -0,0 +1,3 @@
<ds-field-wrapper *ngIf="introductoryText" class="collection-page-introductory-text">
<p [innerHtml]="introductoryText"></p>
</ds-field-wrapper>

View File

@@ -0,0 +1 @@
@import '../../../styles/variables.scss';

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -0,0 +1 @@
@import '../../../styles/variables.scss';

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -0,0 +1 @@
@import '../../../styles/variables.scss';

View File

@@ -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;
}

View File

@@ -0,0 +1 @@
<h1 *ngIf="name">{{ name }}</h1>

View File

@@ -0,0 +1 @@
@import '../../../styles/variables.scss';

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -0,0 +1 @@
@import '../../../styles/variables.scss';

View File

@@ -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;
}

View File

@@ -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
);
}
}

View File

@@ -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
*/

View File

@@ -17,6 +17,8 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
/**
* The Bitstream that represents the logo of this Collection
*/
@autoserialize
@relationship(NormalizedDSOType.NormalizedBitstream)
logo: string;
/**

View File

@@ -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

View File

@@ -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[]>
}

View File

@@ -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[]>;
}

View File

@@ -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[]>;
}

View File

@@ -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

View 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));
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -1,4 +1,4 @@
@import '../../../styles/variables.scss';
@import '../../../../styles/variables.scss';
:host {
.simple-view-element {
margin-bottom: 15px;

View File

@@ -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'],

View File

@@ -0,0 +1 @@
@import '../../../../styles/variables.scss';

View File

@@ -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'],

View File

@@ -0,0 +1 @@
@import '../../../../styles/variables.scss';

View File

@@ -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'],

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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);
}
)
)
}
}

View 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>

View File

@@ -0,0 +1,7 @@
@import '../../../styles/variables.scss';
:host {
div.simple-view-link {
text-align: center;
margin: 20px;
}
}

View 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);
}
}

View File

@@ -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 },
])
]
})

View File

@@ -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,

View File

@@ -1 +0,0 @@
@import '../../../styles/variables.scss';

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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({

View File

@@ -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({

View File

@@ -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({

View File

@@ -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() {
}
}

View File

@@ -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({

View File

@@ -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({

View File

@@ -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>

View File

@@ -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());
}
}

View File

@@ -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() {
}
}

View File

@@ -9,6 +9,7 @@ import { Pipe, PipeTransform } from '@angular/core';
* {{ 1024 | fileSize}}
* formats to: 1 KB
*/
@Pipe({name: 'dsFileSize'})
export class FileSizePipe implements PipeTransform {

View File

@@ -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) {}

View 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);
});
});

View File

@@ -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'],

View File

@@ -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"
},
]
};

View File

@@ -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" }
]
}
]
};

View File

@@ -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",

View File

@@ -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",
}
]
}
}
]
};

View File

@@ -10,5 +10,5 @@
* ];
**/
export const routes: string[] = [
'home', 'items/:id' , '**'
'home', 'items/:id' , 'collections/:id', '**'
];

View File

@@ -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' });
}
}
/**

View File

@@ -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: