diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000..5889e7a85c
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,21 @@
+.git
+node-modules
+__build__
+__server_build__
+typings
+tsd_typings
+npm-debug.log
+dist
+coverage
+.idea
+*.iml
+*.ngfactory.ts
+*.css.shim.ts
+*.scss.shim.ts
+.DS_Store
+webpack.records.json
+npm-debug.log.*
+morgan.log
+yarn-error.log
+*.css
+package-lock.json
diff --git a/.travis.yml b/.travis.yml
index ce1213f483..c35e903d0e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,8 +10,8 @@ addons:
language: node_js
node_js:
- - "6"
- "8"
+ - "9"
cache:
yarn: true
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000..f10164ebd0
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,10 @@
+# This image will be published as dspace/dspace-angular
+# See https://dspace-labs.github.io/DSpace-Docker-Images/ for usage details
+
+FROM node:8-alpine
+WORKDIR /app
+ADD . /app/
+EXPOSE 3000
+
+RUN yarn install
+CMD yarn run watch
diff --git a/README.md b/README.md
index 466e736de2..8f2320dbf3 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ If you're looking for the 2016 Angular 2 DSpace UI prototype, you can find it [h
Quick start
-----------
-**Ensure you're running [Node](https://nodejs.org) >= `v6.9.x`, [npm](https://www.npmjs.com/) >= `v3.x` and [yarn](https://yarnpkg.com) >= `v0.20.x`**
+**Ensure you're running [Node](https://nodejs.org) >= `v8.0.x`, [npm](https://www.npmjs.com/) >= `v3.x` and [yarn](https://yarnpkg.com) >= `v0.20.x`**
```bash
# clone the repo
@@ -23,9 +23,6 @@ git clone https://github.com/DSpace/dspace-angular.git
# change directory to our repo
cd dspace-angular
-# install the global dependencies
-yarn run global
-
# install the local dependencies
yarn install
diff --git a/config/environment.default.js b/config/environment.default.js
index 4f3aee5f0e..a6ef738f41 100644
--- a/config/environment.default.js
+++ b/config/environment.default.js
@@ -22,6 +22,14 @@ module.exports = {
// msToLive: 1000, // 15 minutes
control: 'max-age=60' // revalidate browser
},
+ // Form settings
+ form: {
+ // NOTE: Map server-side validators to comparative Angular form validators
+ validatorMap: {
+ required: 'required',
+ regex: 'pattern'
+ }
+ },
// Notifications
notifications: {
rtl: false,
diff --git a/package.json b/package.json
index 2878daf1c8..0936b27ea4 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
},
"license": "BSD-2-Clause",
"engines": {
- "node": ">=6.0.0"
+ "node": ">=8.0.0"
},
"scripts": {
"global": "npm install -g @angular/cli marked node-gyp nodemon node-nightly npm-check-updates npm-run-all rimraf typescript ts-node typedoc webpack webpack-bundle-analyzer pm2 rollup",
@@ -80,16 +80,22 @@
"@angular/router": "^5.2.5",
"@angularclass/bootloader": "1.0.1",
"@ng-bootstrap/ng-bootstrap": "^1.0.0",
+ "@ng-dynamic-forms/core": "5.4.7",
+ "@ng-dynamic-forms/ui-ng-bootstrap": "5.4.7",
"@ngrx/effects": "^5.1.0",
"@ngrx/router-store": "^5.0.1",
"@ngrx/store": "^5.1.0",
- "@nguniversal/express-engine": "5.0.0-beta.5",
+ "@nguniversal/express-engine": "5.0.0",
"@ngx-translate/core": "9.1.1",
"@ngx-translate/http-loader": "2.0.1",
+ "@nicky-lenaers/ngx-scroll-to": "^0.6.0",
"angular-idle-preload": "2.0.4",
+ "angular2-moment": "^1.9.0",
+ "angular-sortablejs": "^2.5.0",
+ "angular2-text-mask": "8.0.4",
"angulartics2": "^5.2.0",
"body-parser": "1.18.2",
- "bootstrap": "^4.0.0",
+ "bootstrap": "4.1.1",
"cerialize": "0.1.18",
"compression": "1.7.1",
"cookie-parser": "1.4.3",
@@ -99,14 +105,23 @@
"font-awesome": "4.7.0",
"http-server": "0.11.1",
"https": "1.0.0",
+ "js-cookie": "2.2.0",
"js.clone": "0.0.3",
"jsonschema": "1.2.2",
+ "jwt-decode": "^2.2.0",
"methods": "1.1.2",
+ "moment": "^2.22.1",
"morgan": "1.9.0",
+ "ng2-nouislider": "^1.7.11",
+ "ng2-file-upload": "1.2.1",
+ "ngx-infinite-scroll": "0.8.2",
"ngx-pagination": "3.0.3",
+ "nouislider": "^11.0.0",
"pem": "1.12.3",
"reflect-metadata": "0.1.12",
"rxjs": "5.5.6",
+ "sortablejs": "1.7.0",
+ "text-mask-core": "5.0.1",
"ts-md5": "^1.2.4",
"uuid": "^3.2.1",
"webfontloader": "1.6.28",
@@ -124,6 +139,7 @@
"@types/express-serve-static-core": "4.11.1",
"@types/hammerjs": "2.0.35",
"@types/jasmine": "^2.8.6",
+ "@types/js-cookie": "2.1.0",
"@types/memory-cache": "0.2.0",
"@types/mime": "2.0.0",
"@types/node": "^9.4.6",
diff --git a/resources/i18n/en.json b/resources/i18n/en.json
index afd1c48e94..cfba749f17 100644
--- a/resources/i18n/en.json
+++ b/resources/i18n/en.json
@@ -114,7 +114,9 @@
}
},
"nav": {
- "home": "Home"
+ "home": "Home",
+ "login": "Log In",
+ "logout": "Log Out"
},
"pagination": {
"results-per-page": "Results Per Page",
@@ -125,8 +127,13 @@
}
},
"sorting": {
- "ASC": "Ascending",
- "DESC": "Descending"
+ "score": {
+ "DESC": "Relevance"
+ },
+ "dc.title": {
+ "ASC": "Title Ascending",
+ "DESC": "Title Descending"
+ }
},
"title": "DSpace",
"404": {
@@ -177,13 +184,13 @@
"close": "Back to results",
"open": "Search Tools",
"results": "results",
- "filters":{
- "title":"Filters"
+ "filters": {
+ "title": "Filters"
},
- "settings":{
- "title":"Settings",
- "sort-by":"Sort By",
- "rpp":"Results per page"
+ "settings": {
+ "title": "Settings",
+ "sort-by": "Sort By",
+ "rpp": "Results per page"
}
},
"view-switch": {
@@ -193,6 +200,13 @@
"filters": {
"head": "Filters",
"reset": "Reset filters",
+ "applied": {
+ "f.author": "Author",
+ "f.dateIssued.min": "Start date",
+ "f.dateIssued.max": "End date",
+ "f.subject": "Subject",
+ "f.has_content_in_original_bundle": "Has files"
+ },
"filter": {
"show-more": "Show more",
"show-less": "Collapse",
@@ -209,11 +223,15 @@
"head": "Subject"
},
"dateIssued": {
- "placeholder": "Date",
+ "max": {
+ "placeholder": "Minimum Date"
+ },
+ "min": {
+ "placeholder": "Maximum Date"
+ },
"head": "Date"
},
"has_content_in_original_bundle": {
- "placeholder": "Has files",
"head": "Has files"
},
"entityType": {
@@ -223,6 +241,55 @@
}
}
},
+ "admin": {
+ "registries": {
+ "metadata": {
+ "title": "DSpace Angular :: Metadata Registry",
+ "head": "Metadata Registry",
+ "description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.",
+ "schemas": {
+ "table": {
+ "id": "ID",
+ "namespace": "Namespace",
+ "name": "Name"
+ },
+ "no-items": "No metadata schemas to show."
+ }
+ },
+ "schema": {
+ "title": "DSpace Angular :: Metadata Schema Registry",
+ "head": "Metadata Schema",
+ "description": "This is the metadata schema for \"{{namespace}}\".",
+ "fields": {
+ "head": "Schema metadata fields",
+ "table": {
+ "field": "Field",
+ "scopenote": "Scope Note"
+ },
+ "no-items": "No metadata fields to show."
+ }
+ },
+ "bitstream-formats": {
+ "title": "DSpace Angular :: Bitstream Format Registry",
+ "head": "Bitstream Format Registry",
+ "description": "This list of bitstream formats provides information about known formats and their support level.",
+ "formats": {
+ "table": {
+ "name": "Name",
+ "mimetype": "MIME Type",
+ "supportLevel": {
+ "head": "Support Level",
+ "0": "Unknown",
+ "1": "Known",
+ "2": "Support"
+ },
+ "internal": "internal"
+ },
+ "no-items": "No bitstream formats to show."
+ }
+ }
+ }
+ },
"loading": {
"default": "Loading...",
"top-level-communities": "Loading top level communities...",
@@ -243,6 +310,53 @@
"recent-submissions": "Error fetching recent submissions",
"item": "Error fetching item",
"objects": "Error fetching objects",
- "search-results": "Error fetching search results"
+ "search-results": "Error fetching search results",
+ "validation": {
+ "pattern": "This input is restricted by the current pattern: {{ pattern }}.",
+ "license": {
+ "notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission."
+ }
+ }
+ },
+ "form": {
+ "submit": "Submit",
+ "cancel": "Cancel",
+ "search": "Search",
+ "remove": "Remove",
+ "first-name": "First name",
+ "last-name": "Last name",
+ "loading": "Loading...",
+ "no-results": "No results found",
+ "no-value": "No value entered",
+ "group-collapse": "Collapse",
+ "group-expand": "Expand",
+ "group-collapse-help": "Click here to collapse",
+ "group-expand-help": "Click here to expand and add more element"
+ },
+ "login": {
+ "title": "Login",
+ "form": {
+ "header": "Please log in to DSpace",
+ "email": "Email address",
+ "forgot-password": "Have you forgotten your password?",
+ "new-user": "New user? Click here to register.",
+ "password": "Password",
+ "submit": "Log in"
+ }
+ },
+ "logout": {
+ "title": "Logout",
+ "form": {
+ "header": "Log out from DSpace",
+ "submit": "Log out"
+ }
+ },
+ "auth": {
+ "messages": {
+ "expired": "Your session has expired. Please log in again."
+ },
+ "errors": {
+ "invalid-user": "Invalid email or password."
+ }
}
}
diff --git a/src/app/+admin/admin-registries/admin-registries-routing.module.ts b/src/app/+admin/admin-registries/admin-registries-routing.module.ts
new file mode 100644
index 0000000000..8e3c322bc8
--- /dev/null
+++ b/src/app/+admin/admin-registries/admin-registries-routing.module.ts
@@ -0,0 +1,18 @@
+import { MetadataRegistryComponent } from './metadata-registry/metadata-registry.component';
+import { RouterModule } from '@angular/router';
+import { NgModule } from '@angular/core';
+import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
+import { BitstreamFormatsComponent } from './bitstream-formats/bitstream-formats.component';
+
+@NgModule({
+ imports: [
+ RouterModule.forChild([
+ { path: 'metadata', component: MetadataRegistryComponent, data: { title: 'admin.registries.metadata.title' } },
+ { path: 'metadata/:schemaName', component: MetadataSchemaComponent, data: { title: 'admin.registries.schema.title' } },
+ { path: 'bitstream-formats', component: BitstreamFormatsComponent, data: { title: 'admin.registries.bitstream-formats.title' } },
+ ])
+ ]
+})
+export class AdminRegistriesRoutingModule {
+
+}
diff --git a/src/app/+admin/admin-registries/admin-registries.module.ts b/src/app/+admin/admin-registries/admin-registries.module.ts
new file mode 100644
index 0000000000..8ff42646ac
--- /dev/null
+++ b/src/app/+admin/admin-registries/admin-registries.module.ts
@@ -0,0 +1,27 @@
+import { NgModule } from '@angular/core';
+import { MetadataRegistryComponent } from './metadata-registry/metadata-registry.component';
+import { AdminRegistriesRoutingModule } from './admin-registries-routing.module';
+import { CommonModule } from '@angular/common';
+import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
+import { RouterModule } from '@angular/router';
+import { TranslateModule } from '@ngx-translate/core';
+import { BitstreamFormatsComponent } from './bitstream-formats/bitstream-formats.component';
+import { SharedModule } from '../../shared/shared.module';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ SharedModule,
+ RouterModule,
+ TranslateModule,
+ AdminRegistriesRoutingModule
+ ],
+ declarations: [
+ MetadataRegistryComponent,
+ MetadataSchemaComponent,
+ BitstreamFormatsComponent
+ ]
+})
+export class AdminRegistriesModule {
+
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
new file mode 100644
index 0000000000..1ac547653f
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
@@ -0,0 +1,42 @@
+
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
new file mode 100644
index 0000000000..f720c336e5
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
@@ -0,0 +1,98 @@
+import { BitstreamFormatsComponent } from './bitstream-formats.component';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { RegistryService } from '../../../core/registry/registry.service';
+import { Observable } from 'rxjs/Observable';
+import { RemoteData } from '../../../core/data/remote-data';
+import { PaginatedList } from '../../../core/data/paginated-list';
+import { CommonModule } from '@angular/common';
+import { RouterTestingModule } from '@angular/router/testing';
+import { TranslateModule } from '@ngx-translate/core';
+import { By } from '@angular/platform-browser';
+import { SharedModule } from '../../../shared/shared.module';
+import { PaginationComponent } from '../../../shared/pagination/pagination.component';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
+import { HostWindowService } from '../../../shared/host-window.service';
+import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
+
+describe('BitstreamFormatsComponent', () => {
+ let comp: BitstreamFormatsComponent;
+ let fixture: ComponentFixture;
+ let registryService: RegistryService;
+ const mockFormatsList = [
+ {
+ shortDescription: 'Unknown',
+ description: 'Unknown data format',
+ mimetype: 'application/octet-stream',
+ supportLevel: 0,
+ internal: false,
+ extensions: null
+ },
+ {
+ shortDescription: 'License',
+ description: 'Item-specific license agreed upon to submission',
+ mimetype: 'text/plain; charset=utf-8',
+ supportLevel: 1,
+ internal: true,
+ extensions: null
+ },
+ {
+ shortDescription: 'CC License',
+ description: 'Item-specific Creative Commons license agreed upon to submission',
+ mimetype: 'text/html; charset=utf-8',
+ supportLevel: 2,
+ internal: true,
+ extensions: null
+ },
+ {
+ shortDescription: 'Adobe PDF',
+ description: 'Adobe Portable Document Format',
+ mimetype: 'application/pdf',
+ supportLevel: 0,
+ internal: false,
+ extensions: null
+ }
+ ];
+ const mockFormats = Observable.of(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList)));
+ const registryServiceStub = {
+ getBitstreamFormats: () => mockFormats
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
+ providers: [
+ { provide: RegistryService, useValue: registryServiceStub },
+ { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(BitstreamFormatsComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ registryService = (comp as any).service;
+ });
+
+ it('should contain four formats', () => {
+ const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement;
+ expect(tbody.children.length).toBe(4);
+ });
+
+ it('should contain the correct formats', () => {
+ const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(1)')).nativeElement;
+ expect(unknownName.textContent).toBe('Unknown');
+
+ const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(1)')).nativeElement;
+ expect(licenseName.textContent).toBe('License');
+
+ const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(1)')).nativeElement;
+ expect(ccLicenseName.textContent).toBe('CC License');
+
+ const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(1)')).nativeElement;
+ expect(adobeName.textContent).toBe('Adobe PDF');
+ });
+
+});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
new file mode 100644
index 0000000000..d6c84ac858
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
@@ -0,0 +1,33 @@
+import { Component } from '@angular/core';
+import { RegistryService } from '../../../core/registry/registry.service';
+import { Observable } from 'rxjs/Observable';
+import { RemoteData } from '../../../core/data/remote-data';
+import { PaginatedList } from '../../../core/data/paginated-list';
+import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model';
+import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
+
+@Component({
+ selector: 'ds-bitstream-formats',
+ templateUrl: './bitstream-formats.component.html'
+})
+export class BitstreamFormatsComponent {
+
+ bitstreamFormats: Observable>>;
+ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
+ id: 'registry-bitstreamformats-pagination',
+ pageSize: 10000
+ });
+
+ constructor(private registryService: RegistryService) {
+ this.updateFormats();
+ }
+
+ onPageChange(event) {
+ this.config.currentPage = event;
+ this.updateFormats();
+ }
+
+ private updateFormats() {
+ this.bitstreamFormats = this.registryService.getBitstreamFormats(this.config);
+ }
+}
diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html
new file mode 100644
index 0000000000..49a52cec9c
--- /dev/null
+++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html
@@ -0,0 +1,42 @@
+
diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts
new file mode 100644
index 0000000000..e3b2e1f2c1
--- /dev/null
+++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts
@@ -0,0 +1,72 @@
+import { MetadataRegistryComponent } from './metadata-registry.component';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { Observable } from 'rxjs/Observable';
+import { RemoteData } from '../../../core/data/remote-data';
+import { PaginatedList } from '../../../core/data/paginated-list';
+import { TranslateModule } from '@ngx-translate/core';
+import { By } from '@angular/platform-browser';
+import { CommonModule } from '@angular/common';
+import { RouterTestingModule } from '@angular/router/testing';
+import { RegistryService } from '../../../core/registry/registry.service';
+import { SharedModule } from '../../../shared/shared.module';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
+import { PaginationComponent } from '../../../shared/pagination/pagination.component';
+import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
+import { HostWindowService } from '../../../shared/host-window.service';
+
+describe('MetadataRegistryComponent', () => {
+ let comp: MetadataRegistryComponent;
+ let fixture: ComponentFixture;
+ let registryService: RegistryService;
+ const mockSchemasList = [
+ {
+ id: 1,
+ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadataschemas/1',
+ prefix: 'dc',
+ namespace: 'http://dublincore.org/documents/dcmi-terms/'
+ },
+ {
+ id: 2,
+ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadataschemas/2',
+ prefix: 'mock',
+ namespace: 'http://dspace.org/mockschema'
+ }
+ ];
+ const mockSchemas = Observable.of(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockSchemasList)));
+ const registryServiceStub = {
+ getMetadataSchemas: () => mockSchemas
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [MetadataRegistryComponent, PaginationComponent, EnumKeysPipe],
+ providers: [
+ { provide: RegistryService, useValue: registryServiceStub },
+ { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MetadataRegistryComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ registryService = (comp as any).service;
+ });
+
+ it('should contain two schemas', () => {
+ const tbody: HTMLElement = fixture.debugElement.query(By.css('#metadata-schemas>tbody')).nativeElement;
+ expect(tbody.children.length).toBe(2);
+ });
+
+ it('should contain the correct schemas', () => {
+ const dcName: HTMLElement = fixture.debugElement.query(By.css('#metadata-schemas tr:nth-child(1) td:nth-child(3)')).nativeElement;
+ expect(dcName.textContent).toBe('dc');
+
+ const mockName: HTMLElement = fixture.debugElement.query(By.css('#metadata-schemas tr:nth-child(2) td:nth-child(3)')).nativeElement;
+ expect(mockName.textContent).toBe('mock');
+ });
+
+});
diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts
new file mode 100644
index 0000000000..15dc6b0d80
--- /dev/null
+++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts
@@ -0,0 +1,34 @@
+import { Component } from '@angular/core';
+import { RegistryService } from '../../../core/registry/registry.service';
+import { Observable } from 'rxjs/Observable';
+import { RemoteData } from '../../../core/data/remote-data';
+import { PaginatedList } from '../../../core/data/paginated-list';
+import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
+import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
+
+@Component({
+ selector: 'ds-metadata-registry',
+ templateUrl: './metadata-registry.component.html'
+})
+export class MetadataRegistryComponent {
+
+ metadataSchemas: Observable>>;
+ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
+ id: 'registry-metadataschemas-pagination',
+ pageSize: 10000
+ });
+
+ constructor(private registryService: RegistryService) {
+ this.updateSchemas();
+ }
+
+ onPageChange(event) {
+ this.config.currentPage = event;
+ this.updateSchemas();
+ }
+
+ private updateSchemas() {
+ this.metadataSchemas = this.registryService.getMetadataSchemas(this.config);
+ }
+
+}
diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html
new file mode 100644
index 0000000000..e9734888ae
--- /dev/null
+++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html
@@ -0,0 +1,41 @@
+
diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts
new file mode 100644
index 0000000000..7e6064ddff
--- /dev/null
+++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts
@@ -0,0 +1,121 @@
+import { MetadataSchemaComponent } from './metadata-schema.component';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { Observable } from 'rxjs/Observable';
+import { RemoteData } from '../../../core/data/remote-data';
+import { PaginatedList } from '../../../core/data/paginated-list';
+import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { CommonModule } from '@angular/common';
+import { ActivatedRoute, Router } from '@angular/router';
+import { By } from '@angular/platform-browser';
+import { MockTranslateLoader } from '../../../shared/testing/mock-translate-loader';
+import { RegistryService } from '../../../core/registry/registry.service';
+import { SharedModule } from '../../../shared/shared.module';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
+import { PaginationComponent } from '../../../shared/pagination/pagination.component';
+import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
+import { HostWindowService } from '../../../shared/host-window.service';
+import { RouterStub } from '../../../shared/testing/router-stub';
+import { RouterTestingModule } from '@angular/router/testing';
+import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub';
+
+describe('MetadataSchemaComponent', () => {
+ let comp: MetadataSchemaComponent;
+ let fixture: ComponentFixture;
+ let registryService: RegistryService;
+ const mockSchemasList = [
+ {
+ id: 1,
+ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadataschemas/1',
+ prefix: 'dc',
+ namespace: 'http://dublincore.org/documents/dcmi-terms/'
+ },
+ {
+ id: 2,
+ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadataschemas/2',
+ prefix: 'mock',
+ namespace: 'http://dspace.org/mockschema'
+ }
+ ];
+ const mockFieldsList = [
+ {
+ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadatafields/8',
+ element: 'contributor',
+ qualifier: 'advisor',
+ scopenote: null,
+ schema: mockSchemasList[0]
+ },
+ {
+ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadatafields/9',
+ element: 'contributor',
+ qualifier: 'author',
+ scopenote: null,
+ schema: mockSchemasList[0]
+ },
+ {
+ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadatafields/10',
+ element: 'contributor',
+ qualifier: 'editor',
+ scopenote: 'test scope note',
+ schema: mockSchemasList[1]
+ },
+ {
+ self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadatafields/11',
+ element: 'contributor',
+ qualifier: 'illustrator',
+ scopenote: null,
+ schema: mockSchemasList[1]
+ }
+ ];
+ const mockSchemas = Observable.of(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockSchemasList)));
+ const registryServiceStub = {
+ getMetadataSchemas: () => mockSchemas,
+ getMetadataFieldsBySchema: (schema: MetadataSchema) => Observable.of(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFieldsList.filter((value) => value.schema === schema)))),
+ getMetadataSchemaByName: (schemaName: string) => Observable.of(new RemoteData(false, false, true, undefined, mockSchemasList.filter((value) => value.prefix === schemaName)[0]))
+ };
+ const schemaNameParam = 'mock';
+ const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
+ params: Observable.of({
+ schemaName: schemaNameParam
+ })
+ });
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [MetadataSchemaComponent, PaginationComponent, EnumKeysPipe],
+ providers: [
+ { provide: RegistryService, useValue: registryServiceStub },
+ { provide: ActivatedRoute, useValue: activatedRouteStub },
+ { provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
+ { provide: Router, useValue: new RouterStub() }
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MetadataSchemaComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ registryService = (comp as any).service;
+ });
+
+ it('should contain the schema prefix in the header', () => {
+ const header: HTMLElement = fixture.debugElement.query(By.css('.metadata-schema #header')).nativeElement;
+ expect(header.textContent).toContain('mock');
+ });
+
+ it('should contain two fields', () => {
+ const tbody: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields>tbody')).nativeElement;
+ expect(tbody.children.length).toBe(2);
+ });
+
+ it('should contain the correct fields', () => {
+ const editorField: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields tr:nth-child(1) td:nth-child(1)')).nativeElement;
+ expect(editorField.textContent).toBe('mock.contributor.editor');
+
+ const illustratorField: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields tr:nth-child(2) td:nth-child(1)')).nativeElement;
+ expect(illustratorField.textContent).toBe('mock.contributor.illustrator');
+ });
+});
diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts
new file mode 100644
index 0000000000..2f0bfdeddb
--- /dev/null
+++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts
@@ -0,0 +1,55 @@
+import { Component, OnInit } from '@angular/core';
+import { RegistryService } from '../../../core/registry/registry.service';
+import { ActivatedRoute } from '@angular/router';
+import { Observable } from 'rxjs/Observable';
+import { RemoteData } from '../../../core/data/remote-data';
+import { PaginatedList } from '../../../core/data/paginated-list';
+import { MetadataField } from '../../../core/metadata/metadatafield.model';
+import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
+import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
+import { SortOptions } from '../../../core/cache/models/sort-options.model';
+
+@Component({
+ selector: 'ds-metadata-schema',
+ templateUrl: './metadata-schema.component.html'
+})
+export class MetadataSchemaComponent implements OnInit {
+
+ namespace;
+
+ metadataSchema: Observable>;
+ metadataFields: Observable>>;
+ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
+ id: 'registry-metadatafields-pagination',
+ pageSize: 10000
+ });
+
+ constructor(private registryService: RegistryService, private route: ActivatedRoute) {
+
+ }
+
+ ngOnInit(): void {
+ this.route.params.subscribe((params) => {
+ this.initialize(params);
+ });
+ }
+
+ initialize(params) {
+ this.metadataSchema = this.registryService.getMetadataSchemaByName(params.schemaName);
+ this.updateFields();
+ }
+
+ onPageChange(event) {
+ this.config.currentPage = event;
+ this.updateFields();
+ }
+
+ private updateFields() {
+ this.metadataSchema.subscribe((schemaData) => {
+ const schema = schemaData.payload;
+ this.metadataFields = this.registryService.getMetadataFieldsBySchema(schema, this.config);
+ this.namespace = { namespace: schemaData.payload.namespace };
+ });
+ }
+
+}
diff --git a/src/app/+admin/admin-routing.module.ts b/src/app/+admin/admin-routing.module.ts
new file mode 100644
index 0000000000..e7c96bb9c4
--- /dev/null
+++ b/src/app/+admin/admin-routing.module.ts
@@ -0,0 +1,13 @@
+import { RouterModule } from '@angular/router';
+import { NgModule } from '@angular/core';
+
+@NgModule({
+ imports: [
+ RouterModule.forChild([
+ { path: 'registries', loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule' }
+ ])
+ ]
+})
+export class AdminRoutingModule {
+
+}
diff --git a/src/app/+admin/admin.module.ts b/src/app/+admin/admin.module.ts
new file mode 100644
index 0000000000..b979813376
--- /dev/null
+++ b/src/app/+admin/admin.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { AdminRegistriesModule } from './admin-registries/admin-registries.module';
+import { AdminRoutingModule } from './admin-routing.module';
+
+@NgModule({
+ imports: [
+ AdminRegistriesModule,
+ AdminRoutingModule
+ ]
+})
+export class AdminModule {
+
+}
diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts
index c886aa655c..ca56bca2cd 100644
--- a/src/app/+collection-page/collection-page-routing.module.ts
+++ b/src/app/+collection-page/collection-page-routing.module.ts
@@ -2,12 +2,23 @@ import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CollectionPageComponent } from './collection-page.component';
+import { CollectionPageResolver } from './collection-page.resolver';
@NgModule({
imports: [
RouterModule.forChild([
- { path: ':id', component: CollectionPageComponent, pathMatch: 'full' }
+ {
+ path: ':id',
+ component: CollectionPageComponent,
+ pathMatch: 'full',
+ resolve: {
+ collection: CollectionPageResolver
+ }
+ }
])
+ ],
+ providers: [
+ CollectionPageResolver,
]
})
export class CollectionPageRoutingModule {
diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html
index 05b4d6f11e..a233163070 100644
--- a/src/app/+collection-page/collection-page.component.html
+++ b/src/app/+collection-page/collection-page.component.html
@@ -1,6 +1,6 @@
+ *ngVar="(collectionRD$ | async) as collectionRD">
@@ -8,8 +8,8 @@
[name]="collection.name">
-
@@ -38,14 +38,14 @@
-
+
{{'collection.page.browse.recent.head' | translate}}
diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts
index 4a935b73b9..89567c4a54 100644
--- a/src/app/+collection-page/collection-page.component.ts
+++ b/src/app/+collection-page/collection-page.component.ts
@@ -5,7 +5,6 @@ import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
import { CollectionDataService } from '../core/data/collection-data.service';
-import { ItemDataService } from '../core/data/item-data.service';
import { PaginatedList } from '../core/data/paginated-list';
import { RemoteData } from '../core/data/remote-data';
@@ -18,6 +17,11 @@ import { Item } from '../core/shared/item.model';
import { fadeIn, fadeInOut } from '../shared/animations/fade';
import { hasValue, isNotEmpty } from '../shared/empty.util';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
+import { filter, flatMap, map } from 'rxjs/operators';
+import { SearchService } from '../+search-page/search-service/search.service';
+import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
+import { toDSpaceObjectListRD } from '../core/shared/operators';
+import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
@Component({
selector: 'ds-collection-page',
@@ -30,9 +34,9 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp
]
})
export class CollectionPageComponent implements OnInit, OnDestroy {
- collectionRDObs: Observable>;
- itemRDObs: Observable>>;
- logoRDObs: Observable>;
+ collectionRD$: Observable>;
+ itemRD$: Observable>>;
+ logoRD$: Observable>;
paginationConfig: PaginationComponentOptions;
sortConfig: SortOptions;
private subs: Subscription[] = [];
@@ -40,7 +44,7 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
constructor(
private collectionDataService: CollectionDataService,
- private itemDataService: ItemDataService,
+ private searchService: SearchService,
private metadata: MetadataService,
private route: ActivatedRoute
) {
@@ -48,52 +52,41 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
this.paginationConfig.id = 'collection-page-pagination';
this.paginationConfig.pageSize = 5;
this.paginationConfig.currentPage = 1;
- this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
+ this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
}
ngOnInit(): void {
+ this.collectionRD$ = this.route.data.map((data) => data.collection);
+ this.logoRD$ = this.collectionRD$.pipe(
+ map((rd: RemoteData) => rd.payload),
+ filter((collection: Collection) => hasValue(collection)),
+ flatMap((collection: Collection) => collection.logo)
+ );
this.subs.push(
- Observable.combineLatest(
- this.route.params,
- this.route.queryParams,
- (params, queryParams, ) => {
- return Object.assign({}, params, queryParams);
- })
- .subscribe((params) => {
- this.collectionId = params.id;
- this.collectionRDObs = this.collectionDataService.findById(this.collectionId);
- this.metadata.processRemoteData(this.collectionRDObs);
- this.subs.push(this.collectionRDObs
- .map((rd: RemoteData) => rd.payload)
- .filter((collection: Collection) => hasValue(collection))
- .subscribe((collection: Collection) => this.logoRDObs = collection.logo));
-
- const page = +params.page || this.paginationConfig.currentPage;
- const pageSize = +params.pageSize || this.paginationConfig.pageSize;
- const sortDirection = +params.page || this.sortConfig.direction;
- const pagination = Object.assign({},
- this.paginationConfig,
- { currentPage: page, pageSize: pageSize }
- );
- const sort = Object.assign({},
- this.sortConfig,
- { direction: sortDirection, field: params.sortField }
- );
- this.updatePage({
- pagination: pagination,
- sort: sort
- });
- }));
+ this.route.queryParams.subscribe((params) => {
+ this.metadata.processRemoteData(this.collectionRD$);
+ const page = +params.page || this.paginationConfig.currentPage;
+ const pageSize = +params.pageSize || this.paginationConfig.pageSize;
+ const pagination = Object.assign({},
+ this.paginationConfig,
+ { currentPage: page, pageSize: pageSize }
+ );
+ this.updatePage({
+ pagination: pagination,
+ sort: this.sortConfig
+ });
+ }));
}
updatePage(searchOptions) {
- this.itemRDObs = this.itemDataService.findAll({
- scopeID: this.collectionId,
- currentPage: searchOptions.pagination.currentPage,
- elementsPerPage: searchOptions.pagination.pageSize,
- sort: searchOptions.sort
- });
+ this.itemRD$ = this.searchService.search(
+ new PaginatedSearchOptions({
+ scope: this.collectionId,
+ pagination: searchOptions.pagination,
+ sort: searchOptions.sort,
+ dsoType: DSpaceObjectType.ITEM
+ })).pipe(toDSpaceObjectListRD()) as Observable>>;
}
ngOnDestroy(): void {
diff --git a/src/app/+collection-page/collection-page.module.ts b/src/app/+collection-page/collection-page.module.ts
index d0bc918b22..85462e67a3 100644
--- a/src/app/+collection-page/collection-page.module.ts
+++ b/src/app/+collection-page/collection-page.module.ts
@@ -5,11 +5,13 @@ import { SharedModule } from '../shared/shared.module';
import { CollectionPageComponent } from './collection-page.component';
import { CollectionPageRoutingModule } from './collection-page-routing.module';
+import { SearchPageModule } from '../+search-page/search-page.module';
@NgModule({
imports: [
CommonModule,
SharedModule,
+ SearchPageModule,
CollectionPageRoutingModule
],
declarations: [
diff --git a/src/app/+collection-page/collection-page.resolver.ts b/src/app/+collection-page/collection-page.resolver.ts
new file mode 100644
index 0000000000..c049901bf2
--- /dev/null
+++ b/src/app/+collection-page/collection-page.resolver.ts
@@ -0,0 +1,28 @@
+import { Injectable } from '@angular/core';
+import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
+import { Collection } from '../core/shared/collection.model';
+import { Observable } from 'rxjs/Observable';
+import { CollectionDataService } from '../core/data/collection-data.service';
+import { RemoteData } from '../core/data/remote-data';
+import { getSucceededRemoteData } from '../core/shared/operators';
+
+/**
+ * This class represents a resolver that requests a specific collection before the route is activated
+ */
+@Injectable()
+export class CollectionPageResolver implements Resolve> {
+ constructor(private collectionService: CollectionDataService) {
+ }
+
+ /**
+ * Method for resolving a collection based on the parameters in the current route
+ * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
+ * @param {RouterStateSnapshot} state The current RouterStateSnapshot
+ * @returns Observable<> Emits the found collection based on the parameters in the current route
+ */
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> {
+ return this.collectionService.findById(route.params.id).pipe(
+ getSucceededRemoteData()
+ );
+ }
+}
diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts
index 6fd5cc8cb5..4cc927d341 100644
--- a/src/app/+community-page/community-page-routing.module.ts
+++ b/src/app/+community-page/community-page-routing.module.ts
@@ -2,12 +2,23 @@ import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommunityPageComponent } from './community-page.component';
+import { CommunityPageResolver } from './community-page.resolver';
@NgModule({
imports: [
RouterModule.forChild([
- { path: ':id', component: CommunityPageComponent, pathMatch: 'full' }
+ {
+ path: ':id',
+ component: CommunityPageComponent,
+ pathMatch: 'full',
+ resolve: {
+ community: CommunityPageResolver
+ }
+ }
])
+ ],
+ providers: [
+ CommunityPageResolver,
]
})
export class CommunityPageRoutingModule {
diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html
index 976e1091e5..637e37af0c 100644
--- a/src/app/+community-page/community-page.component.html
+++ b/src/app/+community-page/community-page.component.html
@@ -1,11 +1,11 @@
-