1
0

dependency upgrades, server and platform module updates, linting wip

This commit is contained in:
William Welling
2017-07-12 14:33:16 -05:00
parent afc39022f8
commit c08f5c672b
190 changed files with 6321 additions and 4703 deletions

197
README.md
View File

@@ -91,13 +91,15 @@ To change the default configuration values, create local files that override the
To use the configuration parameters in your component: To use the configuration parameters in your component:
```bash ```bash
import { GlobalConfig } from "../config"; import { GLOBAL_CONFIG, GlobalConfig } from '../config';
constructor(@Inject(GLOBAL_CONFIG) public config: GlobalConfig) {}
``` ```
Running the app Running the app
--------------- ---------------
After you have installed all dependencies you can now run the app. Run `yarn run watch:dev` to start a local server which will watch for changes, rebuild the code, and reload the server for you. You can visit it at `http://localhost:3000`. After you have installed all dependencies you can now run the app. Run `yarn run watch` to start a local server which will watch for changes, rebuild the code, and reload the server for you. You can visit it at `http://localhost:3000`.
Running in production mode Running in production mode
-------------------------- --------------------------
@@ -113,7 +115,7 @@ yarn start
If you only want to build for production, without starting, run: If you only want to build for production, without starting, run:
```bash ```bash
yarn run build:prod:ngc:json yarn run build:prod
``` ```
This will build the application and put the result in the `dist` folder This will build the application and put the result in the `dist` folder
@@ -155,7 +157,7 @@ If you are going to use a remote test enviroment you need to edit the './protrac
The default browser is Google Chrome. The default browser is Google Chrome.
Protractor needs a functional instance of the DSpace interface to run the E2E tests, so you need to run:`yarn run watch:dev` Protractor needs a functional instance of the DSpace interface to run the E2E tests, so you need to run:`yarn run watch`
or any command that bring up the DSpace interface. or any command that bring up the DSpace interface.
@@ -171,6 +173,17 @@ To run all the tests (e.g.: to run tests with Continuous Integration software) y
Run:`yarn run docs` to produce the documentation that will be available in the 'doc' folder. Run:`yarn run docs` to produce the documentation that will be available in the 'doc' folder.
Deploy
------
```bash
# deploy production in standalone pm2 container
yarn run deploy
# remove production from standalone pm2 container
yarn run undeploy
```
Other commands Other commands
-------------- --------------
@@ -201,87 +214,105 @@ See [the guide on the wiki](https://wiki.duraspace.org/display/DSPACE/DSpace+7+-
File Structure File Structure
-------------- --------------
Descriptions coming soon...
``` ```
dspace-angular dspace-angular
├── README.md * This document .
├── app.json * Application manifest file ├── README.md
├── config * Folder for configuration files ├── app.yaml
│   └── environment.default.js * Default configuration files ├── config
├── dist * Folder for e2e test files │   ├── environment.default.js
├── e2e * │   ├── environment.dev.js
│   ├── app.e2e-spec.ts * │   ├── environment.prod.js
│   ── app.po.ts * │   ── environment.test.js
│   ├── pagenotfound * ├── e2e
│   │   ├── pagenotfound.e2e-spec.ts * │   ├── app.e2e-spec.ts
│   │   └── pagenotfound.po.ts * │   ├── app.po.ts
│   ── tsconfig.json * │   ── pagenotfound
├── empty.js * │   │   ├── pagenotfound.e2e-spec.ts
├── helpers.js * │   │   └── pagenotfound.po.ts
├── karma.conf.js * Unit Test configuration file │   └── tsconfig.json
├── nodemon.json * Nodemon (https://nodemon.io/) configuration ├── karma.conf.js
├── package.json * This file describes the npm package for this project, its dependencies, scripts, etc. ├── nodemon.json
├── postcss.config.json * PostCSS (http://postcss.org/) configuration file ├── package.json
├── protractor.conf.js * E2E tests configuration file ├── postcss.config.js
├── resources * Folder for static resources ├── protractor.conf.js
│   ├── i18n * Folder for i18n translations ├── resources
│   │   └── en.json * │   ├── data
│   └── images * Folder for images │   │   └── en
│   └── dspace_logo.png * │   ├── i18n
├── rollup-client.js * Rollup (http://rollupjs.org/) configuration for the client │   │   └── en.json
├── rollup-server.js * Rollup (http://rollupjs.org/) configuration for the server │   └── images
── spec-bundle.js * │   └── dspace_logo.png
├── src * The source of the application ├── rollup.config.js
│   ├── app * The location of the app module, and root of the application shared by client and server ├── spec-bundle.js
│   │   ├── app-routing.module.ts * ├── src
│   │   ├── app.component.html * │   ├── app
│   │   ├── app.component.scss * │   │   ├── app-routing.module.ts
│   │   ├── app.component.spec.ts * │   │   ├── app.component.html
│   │   ├── app.component.ts * │   │   ├── app.component.scss
│   │   ├── app.effects.ts * │   │   ├── app.component.spec.ts
│   │   ├── app.module.ts * │   │   ├── app.component.ts
│   │   ├── app.reducers.ts * │   │   ├── app.effects.ts
│   │   ├── core * │   │   ├── app.module.ts
│   │   ├── header * │   │   ├── app.reducer.ts
│   │   ├── home * │   │   ├── browser-app.module.ts
│   │   ├── pagenotfound * │   │   ├── collection-page
│   │   ├── shared * │   │   ├── community-page
│   │   ── store.actions.ts * │   │   ── core
│   ├── backend * Folder containing a mock of the REST API, hosted by the express server │   │   ├── header
│   │   ├── api.ts * │   │   ├── home
│   │   ├── bitstreams.ts * │   │   ├── item-page
│   │   ├── bundles.ts * │   │   ├── object-list
│   │   ├── cache.ts * │   │   ├── pagenotfound
│   │   ├── collections.ts * │   │   ├── server-app.module.ts
│   │   ├── db.ts * │   │   ├── shared
│   │   ├── items.ts * │   │   ├── store.actions.ts
│   │   ── metadata.ts * │   │   ── store.effects.ts
│   ├── client.aot.ts * The bootstrap file for the client, in production │   │   ├── thumbnail
│   ├── client.ts * The bootstrap file for the client, during development │   │   └── typings.d.ts
│   ├── config.ts * File that loads environmental and shareable settings and makes them available to app components │   ├── backend
│   ├── index.html * The index.html file │   │   ├── api.ts
│   ├── platform * │   │   ├── cache.ts
│   │   ├── angular2-meta.ts * │   │   ├── data
│   │   ── modules * │   │   ── db.ts
│   │   │   ├── browser.module.ts * The root module for the client │   ├── config
│   │   │   └── node.module.ts * The root module for the server │   │   ├── cache-config.interface.ts
│   │   ── workarounds * │   │   ── global-config.interface.ts
│   │   ├── __workaround.browser.ts * │   │   └── server-config.interface.ts
│   │   └── __workaround.node.ts * │   ├── config.ts
│   ├── server.aot.ts * The express (http://expressjs.com/) config and bootstrap file for the server, in production │   ├── index.html
│   ├── server.routes.ts * The routes file for the server │   ├── main.browser.ts
│   ├── server.ts * The express (http://expressjs.com/) config and bootstrap file for the server, during development │   ├── main.server.aot.ts
│   ├── styles * Folder containing global styles. │   ├── main.server.ts
│   │   ├── main.scss * Global scss file │   ├── modules
│   │   ── variables.scss * Global sass variables file │   │   ── cookies
│   └── typings.d.ts * File that allows you to add custom typings for libraries without TypeScript support │   │   ├── data-loader
├── tsconfig.aot.json * TypeScript config for production builds │   │   ├── transfer-http
├── tsconfig.json * TypeScript config for development build │   │   ├── transfer-state
├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration │   │   ├── transfer-store
├── typedoc.json * TYPEDOC configuration │   │   └── translate-universal-loader.ts
├── webpack.config.ts * Webpack (https://webpack.github.io/) config for development builds │   ├── routes.ts
├── webpack.prod.config.ts * Webpack (https://webpack.github.io/) config for production builds │   ├── styles
├── webpack.test.config.js * Webpack (https://webpack.github.io/) config for testing │   │   ├── _mixins.scss
└── yarn.lock * Yarn lockfile (https://yarnpkg.com/en/docs/yarn-lock) │   │   └── variables.scss
│   ├── tsconfig.browser.json
│   ├── tsconfig.server.aot.json
│   ├── tsconfig.server.json
│   └── tsconfig.test.json
├── tsconfig.json
├── tslint.json
├── typedoc.json
├── webpack
│   ├── helpers.js
│   ├── webpack.client.js
│   ├── webpack.common.js
│   ├── webpack.prod.js
│   ├── webpack.server.js
│   └── webpack.test.js
├── webpack.config.ts
└── yarn.lock
``` ```
3rd Party Library Installation 3rd Party Library Installation

View File

@@ -1,12 +0,0 @@
{
"name": "Angular 2 Universal Starter",
"description": "Angular 2 Universal starter kit by @AngularClass",
"repository": "https://github.com/angular/universal-starter",
"logo": "https://cloud.githubusercontent.com/assets/1016365/10639063/138338bc-7806-11e5-8057-d34c75f3cafc.png",
"env": {
"NPM_CONFIG_PRODUCTION": {
"description": "Install `webpack` and other development modules when deploying to allow full builds.",
"value": "false"
}
}
}

17
app.yaml Normal file
View File

@@ -0,0 +1,17 @@
# Copyright 2015-2016, Google, Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# [START app_yaml]
runtime: nodejs
env: flex
# [END app_yaml]

View File

@@ -1,28 +1,30 @@
module.exports = { module.exports = {
// The REST API server settings.
"rest": {
"ssl": false,
"address": "dspace7.4science.it",
"port": 80,
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
"nameSpace": "/dspace-spring-rest/api"
},
// Angular2 UI server settings. // Angular2 UI server settings.
"ui": { ui: {
"ssl": false, ssl: false,
"address": "localhost", host: 'localhost',
"port": 3000, port: 3000,
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
"nameSpace": "/" nameSpace: '/'
}, },
"cache": { // The REST API server settings.
rest: {
ssl: false,
host: 'dspace7.4science.it',
port: 80,
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
nameSpace: '/dspace-spring-rest/api'
},
cache: {
// how long should objects be cached for by default // how long should objects be cached for by default
"msToLive": 15 * 60 * 1000, // 15 minute msToLive: 15 * 60 * 1000, // 15 minute
"control": "max-age=60" // revalidate browser control: 'max-age=60' // revalidate browser
}, },
"universal": { logDirectory: '.',
// Angular Universal settings // NOTE: rehydrate or replay
"preboot": true, // rehydrate will transfer prerender state to browser state, actions do not need to replay
"async": true // replay will transfer an array of actions to browser, actions replay automatically
} prerenderStrategy: 'replay',
// NOTE: will log all redux actions and transfers in console
debug: true
}; };

View File

@@ -0,0 +1,30 @@
module.exports = {
// Angular2 UI server settings.
ui: {
ssl: false,
host: 'localhost',
port: 3000,
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
nameSpace: '/'
},
// The REST API server settings.
rest: {
ssl: false,
host: 'dspace7.4science.it',
port: 80,
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
nameSpace: '/dspace-spring-rest/api'
},
cache: {
// how long should objects be cached for by default
msToLive: 15 * 60 * 1000, // 15 minute
control: 'max-age=60' // revalidate browser
},
logDirectory: '.',
// NOTE: rehydrate or replay
// rehydrate will transfer prerender state to browser state, actions do not need to replay
// replay will transfer an array of actions to browser, actions replay automatically
prerenderStrategy: 'rehydrate',
// NOTE: will log all redux actions and transfers in console
debug: true
};

View File

@@ -1,6 +1,6 @@
import { ProtractorPage } from './app.po'; import { ProtractorPage } from './app.po';
describe('protractor App', function() { describe('protractor App', () => {
let page: ProtractorPage; let page: ProtractorPage;
beforeEach(() => { beforeEach(() => {
@@ -9,11 +9,11 @@ describe('protractor App', function() {
it('should display title "DSpace"', () => { it('should display title "DSpace"', () => {
page.navigateTo(); page.navigateTo();
expect(page.getPageTitleText()).toEqual('DSpace'); expect<any>(page.getPageTitleText()).toEqual('DSpace');
}); });
it('should display header "Welcome to DSpace"', () => { it('should display header "Welcome to DSpace"', () => {
page.navigateTo(); page.navigateTo();
expect(page.getFirstHeaderText()).toEqual('Welcome to DSpace'); expect<any>(page.getFirstHeaderText()).toEqual('Welcome to DSpace');
}); });
}); });

View File

@@ -1,19 +1,19 @@
import { ProtractorPage } from './pagenotfound.po'; import { ProtractorPage } from './pagenotfound.po';
describe('protractor PageNotFound', function() { describe('protractor PageNotFound', () => {
let page: ProtractorPage; let page: ProtractorPage;
beforeEach(() => { beforeEach(() => {
page = new ProtractorPage(); page = new ProtractorPage();
}); });
it('should contain element ds-pagenotfound when navigating to page that doesnt exist"', () => { it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => {
page.navigateToNonExistingPage(); page.navigateToNonExistingPage();
expect(page.elementTagExists("ds-pagenotfound")).toEqual(true); expect<any>(page.elementTagExists('ds-pagenotfound')).toEqual(true);
}); });
it('should not contain element ds-pagenotfound when navigating to existing page"', () => { it('should not contain element ds-pagenotfound when navigating to existing page', () => {
page.navigateToExistingPage(); page.navigateToExistingPage();
expect(page.elementTagExists("ds-pagenotfound")).toEqual(false); expect<any>(page.elementTagExists('ds-pagenotfound')).toEqual(false);
}); });
}); });

View File

@@ -1,8 +1,8 @@
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
export class ProtractorPage { export class ProtractorPage {
HOMEPAGE : string = "/home"; HOMEPAGE = '/home';
NONEXISTINGPAGE : string = "/e9019a69-d4f1-4773-b6a3-bd362caa46f2"; NONEXISTINGPAGE = '/e9019a69-d4f1-4773-b6a3-bd362caa46f2';
navigateToNonExistingPage() { navigateToNonExistingPage() {
return browser.get(this.NONEXISTINGPAGE); return browser.get(this.NONEXISTINGPAGE);
@@ -15,5 +15,4 @@ export class ProtractorPage {
return element(by.tagName(tag)).isPresent(); return element(by.tagName(tag)).isPresent();
} }
} }

View File

@@ -1,7 +0,0 @@
module.exports = {
NgProbeToken: {},
_createConditionalRootRenderer: function(rootRenderer, extraTokens, coreTokens) {
return rootRenderer;
},
__platform_browser_private__: {}
};

View File

@@ -1,24 +0,0 @@
/**
* @author: @AngularClass
*/
var path = require('path');
// Helper functions
var ROOT = path.resolve(__dirname, '.');
function hasProcessFlag(flag) {
return process.argv.join('').indexOf(flag) > -1;
}
function isWebpackDevServer() {
return process.argv[1] && !! (/webpack-dev-server/.exec(process.argv[1]));
}
function root(args) {
args = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [ROOT].concat(args));
}
exports.hasProcessFlag = hasProcessFlag;
exports.isWebpackDevServer = isWebpackDevServer;
exports.root = root;

View File

@@ -4,7 +4,9 @@
module.exports = function (config) { module.exports = function (config) {
var testWebpackConfig = require('./webpack.test.config.js')({env: 'test'}); var testWebpackConfig = require('./webpack/webpack.test.js')({
env: 'test'
});
// Uncomment and change to run tests on a remote Selenium server // Uncomment and change to run tests on a remote Selenium server
var webdriverConfig = { var webdriverConfig = {
@@ -15,7 +17,7 @@ module.exports = function(config) {
var configuration = { var configuration = {
// base path that will be used to resolve all patterns (e.g. files, exclude) // base path that will be used to resolve all patterns (e.g. files, exclude)
basePath: '.', basePath: '',
/* /*
* Frameworks to use * Frameworks to use
@@ -33,7 +35,8 @@ module.exports = function(config) {
require('karma-mocha-reporter'), require('karma-mocha-reporter'),
require('karma-remap-istanbul'), require('karma-remap-istanbul'),
require('karma-sourcemap-loader'), require('karma-sourcemap-loader'),
require('karma-webpack') require('karma-webpack'),
require("istanbul-instrumenter-loader")
], ],
// list of files to exclude // list of files to exclude
@@ -44,12 +47,10 @@ module.exports = function(config) {
* *
* we are building the test environment in ./spec-bundle.js * we are building the test environment in ./spec-bundle.js
*/ */
files: [ files: [{
{
pattern: './spec-bundle.js', pattern: './spec-bundle.js',
watched: false watched: false
} }],
],
/* /*
* preprocess matching files before serving them to the browser * preprocess matching files before serving them to the browser
@@ -63,8 +64,7 @@ module.exports = function(config) {
webpack: testWebpackConfig, webpack: testWebpackConfig,
coverageReporter: { coverageReporter: {
reporters: [ reporters: [{
{
type: 'in-memory' type: 'in-memory'
}, { }, {
type: 'json', type: 'json',
@@ -73,8 +73,7 @@ module.exports = function(config) {
}, { }, {
type: 'html', type: 'html',
dir: 'coverage/' dir: 'coverage/'
} }]
]
}, },
remapCoverageReporter: { remapCoverageReporter: {
@@ -89,9 +88,24 @@ module.exports = function(config) {
} }
}, },
// Webpack please don't spam the console when running in karma! /**
* Webpack please don't spam the console when running in karma!
*/
webpackMiddleware: { webpackMiddleware: {
stats: 'errors-only' /**
* webpack-dev-middleware configuration
* i.e.
*/
noInfo: true,
/**
* and use stats to turn off verbose output
*/
stats: {
/**
* options i.e.
*/
chunks: false
}
}, },
/* /*
@@ -114,10 +128,10 @@ module.exports = function(config) {
* level of logging * level of logging
* possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
*/ */
logLevel: config.LOG_INFO, logLevel: config.LOG_WARN,
// enable / disable watching file and executing tests whenever any file changes // enable / disable watching file and executing tests whenever any file changes
//autoWatch: true, autoWatch: false,
/* /*
* start these browsers * start these browsers
@@ -125,9 +139,6 @@ module.exports = function(config) {
*/ */
browsers: [ browsers: [
'Chrome' 'Chrome'
//'ChromeTravisCi',
//'SeleniumChrome',
//'SeleniumFirefox'
], ],
customLaunchers: { customLaunchers: {
@@ -156,11 +167,6 @@ module.exports = function(config) {
browserNoActivityTimeout: 30000 browserNoActivityTimeout: 30000
/*
* Continuous Integration mode
* if true, Karma captures browsers, runs the tests and exits
*/
//singleRun: true
}; };
if (process.env.TRAVIS) { if (process.env.TRAVIS) {

View File

@@ -1,187 +1,197 @@
{ {
"name": "dspace-angular", "name": "dspace-angular",
"version": "0.0.0", "version": "0.0.1",
"description": "Angular 2 Universal UI for DSpace", "description": "Angular Universal UI for DSpace",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/dspace/dspace-angular.git" "url": "https://github.com/dspace/dspace-angular.git"
}, },
"license": "BSD-2-Clause",
"engines": {
"node": ">=5.0.0"
},
"scripts": { "scripts": {
"clean:log": "rimraf *.log*", "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",
"clean:dist": "rimraf dist/*",
"clean:node": "rimraf node_modules",
"clean:ngc": "rimraf **/*.ngfactory.ts",
"clean:json": "rimraf *.records.json",
"clean:css": "rimraf src/**/*.css",
"clean:css:ts": "rimraf src/**/*.css.ts",
"clean:scss:ts": "rimraf src/**/*.scss.ts",
"clean:css:shim:ts": "rimraf src/**/*.css.shim.ts",
"clean:scss:shim:ts": "rimraf src/**/*.scss.shim.ts",
"clean:coverage": "rimraf coverage", "clean:coverage": "rimraf coverage",
"clean:prod": "yarn run clean:ngc && yarn run clean:json && yarn run clean:css && yarn run clean:css:ts && yarn run clean:scss:ts && yarn run clean:css:shim:ts && yarn run clean:scss:shim:ts && yarn run clean:dist", "clean:dist": "rimraf dist",
"clean": "yarn run clean:log && yarn run clean:prod && yarn run clean:coverage && yarn run clean:node", "clean:doc": "rimraf doc",
"sass": "node-sass src -o src --include-path node_modules --output-style compressed -q", "clean:log": "rimraf *.log*",
"postcss": "node node_modules/postcss-cli/bin/postcss -c postcss.config.json", "clean:json": "rimraf *.records.json",
"style": "yarn run sass && yarn run postcss", "clean:node": "rimraf node_modules",
"style:watch": "nodemon -e scss -w src -x \"yarn run style\"", "clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json ",
"rollup": "rollup -c rollup-server.js && rollup -c rollup-client.js", "clean": "yarn run clean:prod && yarn run clean:node",
"prebuild": "yarn run clean:dist && yarn run style", "prebuild": "yarn run clean:dist",
"prebuild:aot": "yarn run prebuild",
"prebuild:prod": "yarn run prebuild",
"build": "webpack --progress", "build": "webpack --progress",
"build:prod": "webpack --config webpack.prod.config.ts", "build:aot": "webpack --env.aot --env.server && webpack --env.aot --env.client",
"build:prod:rollup": "yarn run build:prod && yarn run rollup", "build:prod": "webpack --env.aot --env.server -p && webpack --env.aot --env.client -p",
"build:prod:ngc": "yarn run clean:prod && yarn run style && yarn run ngc && yarn run build:prod:rollup", "postbuild:prod": "yarn run rollup",
"build:prod:ngc:json": "yarn run clean:prod && yarn run style && yarn run ngc && yarn run build:prod:json:rollup", "rollup": "rollup -c rollup.config.js",
"build:prod:json": "webpack --config webpack.prod.config.ts --json | webpack-bundle-size-analyzer", "prestart": "yarn run build:prod",
"build:prod:json:rollup": "yarn run build:prod:json && yarn run rollup", "prestart:dev": "yarn run build",
"ngc": "ngc -p tsconfig.aot.json",
"prestart": "yarn run build:prod:ngc:json",
"server": "node dist/server/index.js",
"server:dev": "node dist/server/index.js",
"server:dev:watch": "nodemon --debug dist/server/index.js",
"start": "yarn run server", "start": "yarn run server",
"start:dev": "yarn run clean:prod && yarn run build && yarn run server:dev", "start:dev": "yarn run server",
"watch": "webpack -w & yarn run style:watch", "deploy": "pm2 start dist/server.js",
"watch:dev:server": "npm-run-all -p server:dev:watch watch", "predeploy": "npm run build:prod",
"watch:dev": "yarn run clean:prod && yarn run build && yarn run watch:dev:server", "preundeploy": "pm2 stop dist/server.js",
"watch:prod:server": "npm-run-all -p server watch", "undeploy": "pm2 delete dist/server.js",
"watch:prod": "yarn run build:prod:ngc:json && yarn run watch:prod:server", "postundeploy": "npm run clean:dist",
"server": "node dist/server.js",
"server:watch": "nodemon dist/server.js",
"server:watch:debug": "nodemon --debug dist/server.js",
"webpack:watch": "webpack -w",
"webpack:watch:aot": "webpack -w --env.aot --env.server && webpack -w --env.aot --env.client",
"webpack:watch:prod": "webpack -w --env.aot --env.server -p && webpack -w --env.aot --env.client -p",
"watch": "yarn run build && npm-run-all -p webpack:watch server:watch",
"watch:aot": "yarn run build:aot && npm-run-all -p webpack:watch:aot server:watch",
"watch:prod": "yarn run build:prod && npm-run-all -p webpack:watch:prod server:watch",
"watch:debug": "yarn run build && npm-run-all -p webpack:watch server:watch:debug",
"watch:debug:aot": "yarn run build:aot && npm-run-all -p webpack:watch:aot server:watch:debug",
"watch:debug:prod": "yarn run build:prod && npm-run-all -p webpack:watch:prod server:watch:debug",
"predebug": "yarn run build", "predebug": "yarn run build",
"debug": "node --debug-brk dist/server/index.js", "predebug:server": "yarn run build",
"debug:server": "node-nightly --inspect --debug-brk dist/server/index.js", "debug": "node --debug-brk dist/server.js",
"debug:start": "yarn run build && yarn run debug:server", "debug:server": "node-nightly --inspect --debug-brk dist/server.js",
"debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js", "debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js",
"debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --config webpack.prod.config.ts", "debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.aot --env.client --env.server -p",
"docs": "typedoc --options typedoc.json ./src/", "ci": "yarn run lint && yarn run build:aot && yarn run test && npm-run-all -p -r server e2e",
"lint": "tslint \"src/**/*.ts\" || true",
"global": "npm install -g @angular/cli nodemon npm-check-updates rimraf ts-node typedoc typescript webpack webpack-bundle-size-analyzer rollup marked node-gyp",
"ci": "yarn run lint && yarn run build:prod:ngc:json && yarn run test && npm-run-all -p -r server e2e",
"protractor": "node node_modules/protractor/bin/protractor", "protractor": "node node_modules/protractor/bin/protractor",
"pree2e": "yarn run webdriver:update", "pree2e": "yarn run webdriver:update",
"e2e": "yarn run protractor", "e2e": "yarn run protractor",
"test": "karma start --single-run", "test": "karma start --single-run",
"test:watch": "karma start --no-single-run --auto-watch", "test:watch": "karma start --no-single-run --auto-watch",
"coverage": "http-server -c-1 -o -p 9875 ./coverage",
"webdriver:start": "node node_modules/protractor/bin/webdriver-manager start --seleniumPort 4444", "webdriver:start": "node node_modules/protractor/bin/webdriver-manager start --seleniumPort 4444",
"webdriver:update": "node node_modules/protractor/bin/webdriver-manager update --standalone" "webdriver:update": "node node_modules/protractor/bin/webdriver-manager update --standalone",
"lint": "tslint \"src/**/*.ts\" || true && tslint \"e2e/**/*.ts\" || true",
"docs": "typedoc --options typedoc.json ./src/",
"coverage": "http-server -c-1 -o -p 9875 ./coverage"
}, },
"dependencies": { "dependencies": {
"@angular/common": "2.2.3", "@angular/animations": "4.2.6",
"@angular/compiler": "2.2.3", "@angular/common": "4.2.6",
"@angular/compiler-cli": "2.2.3", "@angular/core": "4.2.6",
"@angular/core": "2.2.3", "@angular/forms": "4.2.6",
"@angular/forms": "2.2.3", "@angular/http": "4.2.6",
"@angular/http": "2.2.3", "@angular/platform-browser": "4.2.6",
"@angular/platform-browser": "2.2.3", "@angular/platform-browser-dynamic": "4.2.6",
"@angular/platform-browser-dynamic": "2.2.3", "@angular/platform-server": "4.2.6",
"@angular/platform-server": "2.2.3", "@angular/router": "4.2.6",
"@angular/router": "3.2.3",
"@angular/upgrade": "2.2.3",
"@angularclass/bootloader": "1.0.1", "@angularclass/bootloader": "1.0.1",
"@angularclass/idle-preload": "1.0.4", "@angularclass/idle-preload": "1.0.4",
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.18", "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.28",
"@ngrx/core": "^1.2.0", "@ngrx/core": "1.2.0",
"@ngrx/effects": "2.0.2", "@ngrx/effects": "2.0.4",
"@ngrx/router-store": "^1.2.5", "@ngrx/router-store": "1.2.6",
"@ngrx/store": "^2.2.1", "@ngrx/store": "2.2.3",
"@ngrx/store-devtools": "^3.2.2", "@nguniversal/express-engine": "1.0.0-beta.2",
"@ngx-translate/core": "^6.0.1", "@ngx-translate/core": "7.0.0",
"@ngx-translate/http-loader": "^0.0.3", "@ngx-translate/http-loader": "0.1.0",
"@types/jsonschema": "0.0.5", "body-parser": "1.17.2",
"angular2-express-engine": "2.1.0-rc.1",
"angular2-platform-node": "2.1.0-rc.1",
"angular2-universal": "2.1.0-rc.1",
"angular2-universal-polyfills": "2.1.0-rc.1",
"body-parser": "1.15.2",
"bootstrap": "4.0.0-alpha.6", "bootstrap": "4.0.0-alpha.6",
"cerialize": "^0.1.13", "cerialize": "0.1.15",
"compression": "1.6.2", "compression": "1.7.0",
"express": "4.14.0", "cookie-parser": "1.4.3",
"core-js": "2.4.1",
"express": "4.15.3",
"express-session": "1.15.3",
"font-awesome": "4.7.0", "font-awesome": "4.7.0",
"http-server": "^0.9.0", "http-server": "0.10.0",
"https": "1.0.0",
"js.clone": "0.0.3", "js.clone": "0.0.3",
"jsonschema": "^1.1.1", "jsonschema": "1.1.1",
"methods": "1.1.2", "methods": "1.1.2",
"morgan": "1.7.0", "morgan": "1.8.2",
"ng2-pagination": "^2.0.0", "ng2-pagination": "2.0.2",
"preboot": "4.5.2", "pem": "1.9.7",
"reflect-metadata": "^0.1.10", "reflect-metadata": "0.1.10",
"rxjs": "5.0.0-beta.12", "rxjs": "5.4.2",
"ts-md5": "^1.2.0", "ts-md5": "1.2.0",
"webfontloader": "1.6.27", "webfontloader": "1.6.28",
"zone.js": "0.6.26" "zone.js": "0.8.12"
}, },
"devDependencies": { "devDependencies": {
"@ngtools/webpack": "1.1.9", "@angular/compiler": "4.2.6",
"@types/body-parser": "0.0.33", "@angular/compiler-cli": "4.2.6",
"@types/compression": "0.0.33", "@ngrx/store-devtools": "3.2.4",
"@ngtools/webpack": "1.5.0",
"@types/cookie-parser": "1.3.30", "@types/cookie-parser": "1.3.30",
"@types/deep-freeze": "0.0.29", "@types/deep-freeze": "0.1.1",
"@types/express": "4.0.34", "@types/express": "4.0.36",
"@types/express-serve-static-core": "4.0.39", "@types/express-serve-static-core": "4.0.49",
"@types/hammerjs": "2.0.33", "@types/hammerjs": "2.0.34",
"@types/jasmine": "2.5.41", "@types/jasmine": "2.5.53",
"@types/lodash": "ts2.0", "@types/lodash": "ts2.0",
"@types/memory-cache": "0.0.29", "@types/memory-cache": "0.0.29",
"@types/mime": "0.0.29", "@types/mime": "1.3.1",
"@types/morgan": "1.7.32", "@types/node": "8.0.10",
"@types/node": "6.0.52",
"@types/serve-static": "1.7.31", "@types/serve-static": "1.7.31",
"@types/webfontloader": "1.6.27", "@types/source-map": "0.5.0",
"ajv": "4.2.0", "@types/webfontloader": "1.6.28",
"ajv-keywords": "1.1.1", "ajv": "5.2.2",
"angular2-template-loader": "0.6.0", "ajv-keywords": "2.1.0",
"autoprefixer": "6.5.4", "angular2-template-loader": "0.6.2",
"awesome-typescript-loader": "2.2.4", "autoprefixer": "7.1.2",
"codelyzer": "2.0.0-beta.3", "awesome-typescript-loader": "3.2.1",
"cookie-parser": "1.4.3", "codelyzer": "3.1.2",
"compression-webpack-plugin": "0.4.0",
"copy-webpack-plugin": "4.0.1", "copy-webpack-plugin": "4.0.1",
"css-loader": "^0.26.0", "css-loader": "0.28.4",
"deep-freeze": "0.0.1", "deep-freeze": "0.0.1",
"html-webpack-plugin": "^2.21.0", "exports-loader": "0.6.4",
"imports-loader": "0.7.0", "html-webpack-plugin": "2.29.0",
"istanbul-instrumenter-loader": "^0.2.0", "imports-loader": "0.7.1",
"jasmine-core": "~2.5.2", "istanbul-instrumenter-loader": "2.0.0",
"jasmine-spec-reporter": "~2.7.0", "jasmine-core": "2.6.4",
"jasmine-spec-reporter": "4.1.1",
"json-loader": "0.5.4", "json-loader": "0.5.4",
"karma": "^1.2.0", "karma": "1.7.0",
"karma-chrome-launcher": "^2.0.0", "karma-chrome-launcher": "2.2.0",
"karma-cli": "^1.0.1", "karma-cli": "1.0.1",
"karma-coverage": "^1.1.1", "karma-coverage": "1.1.1",
"karma-jasmine": "^1.0.2", "karma-jasmine": "1.1.0",
"karma-mocha-reporter": "^2.0.0", "karma-mocha-reporter": "2.2.3",
"karma-phantomjs-launcher": "^1.0.2", "karma-phantomjs-launcher": "1.0.4",
"karma-remap-istanbul": "^0.2.1", "karma-remap-istanbul": "0.6.0",
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "0.3.7",
"karma-webdriver-launcher": "^1.0.4", "karma-webdriver-launcher": "1.0.5",
"karma-webpack": "1.8.0", "karma-webpack": "2.0.4",
"ngrx-store-freeze": "^0.1.9", "ngrx-store-freeze": "0.1.9",
"node-sass": "4.0.0", "node-sass": "4.5.3",
"nodemon": "1.11.0", "nodemon": "1.11.0",
"npm-run-all": "4.0.2", "npm-run-all": "4.0.2",
"postcss-cli": "^2.6.0", "postcss": "6.0.6",
"protractor": "~4.0.14", "postcss-apply": "0.8.0",
"protractor-istanbul-plugin": "~2.0.0", "postcss-cli": "4.1.0",
"postcss-cssnext": "3.0.2",
"postcss-loader": "2.0.6",
"postcss-responsive-type": "0.5.1",
"postcss-smart-import": "0.7.5",
"protractor": "5.1.2",
"protractor-istanbul-plugin": "2.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"rimraf": "2.5.4", "resolve-url-loader": "2.1.0",
"rollup": "0.37.0", "rimraf": "2.6.1",
"rollup-plugin-commonjs": "6.0.0", "rollup": "0.45.1",
"rollup-plugin-commonjs": "8.0.2",
"rollup-plugin-node-globals": "1.1.0", "rollup-plugin-node-globals": "1.1.0",
"rollup-plugin-node-resolve": "2.0.0", "rollup-plugin-node-resolve": "3.0.0",
"rollup-plugin-uglify": "1.0.1", "rollup-plugin-uglify": "2.0.1",
"source-map-loader": "^0.1.5", "sass-loader": "6.0.6",
"string-replace-loader": "1.0.5", "script-ext-html-webpack-plugin": "1.8.3",
"to-string-loader": "^1.1.4", "source-map-loader": "0.2.1",
"string-replace-loader": "1.3.0",
"to-string-loader": "1.1.5",
"ts-helpers": "1.1.2", "ts-helpers": "1.1.2",
"ts-node": "1.7.2", "ts-node": "3.2.0",
"tslint": "4.0.2", "tslint": "5.1.0",
"tslint-loader": "3.3.0", "tslint-loader": "3.5.3",
"typedoc": "0.5.7", "typedoc": "0.7.1",
"typescript": "2.0.10", "typescript": "2.4.1",
"v8-lazy-parse-webpack-plugin": "0.3.0", "webpack": "2.6.1",
"webpack": "2.1.0-beta.27", "webpack-bundle-analyzer": "2.8.2",
"webpack-bundle-analyzer": "1.4.1", "webpack-dev-middleware": "1.11.0",
"webpack-dev-middleware": "1.9.0", "webpack-dev-server": "2.5.1",
"webpack-dev-server": "2.1.0-beta.11", "webpack-merge": "4.1.0"
"webpack-merge": "1.1.1"
} }
} }

8
postcss.config.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
plugins: [
require('postcss-smart-import')(),
require('postcss-cssnext')(),
require('postcss-apply')(),
require('postcss-responsive-type')()
]
};

View File

@@ -1,9 +0,0 @@
{
"use": ["autoprefixer"],
"input": "src/**/*.css",
"replace": true,
"local-plugins": true,
"autoprefixer": {
"browsers": "last 2 versions"
}
}

View File

@@ -2,7 +2,7 @@
// https://github.com/angular/protractor/blob/master/docs/referenceConf.js // https://github.com/angular/protractor/blob/master/docs/referenceConf.js
/*global jasmine */ /*global jasmine */
var SpecReporter = require('jasmine-spec-reporter'); var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
exports.config = { exports.config = {
allScriptsTimeout: 11000, allScriptsTimeout: 11000,
@@ -80,6 +80,10 @@ exports.config = {
}); });
}, },
onPrepare: function () { onPrepare: function () {
jasmine.getEnv().addReporter(new SpecReporter()); jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: true
}
}));
} }
}; };

0
resources/data/.gitkeep Normal file
View File

View File

View File

@@ -0,0 +1,5 @@
{
"test": {
"message": "Hello, DSpace!"
}
}

View File

@@ -1,16 +0,0 @@
import rollup from 'rollup'
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify'
export default {
entry : 'dist/client/main.bundle.js',
dest : 'dist/client/main.bundle.js',
sourceMap : false,
format : 'iife',
plugins : [
nodeResolve({jsnext: true, module: true}),
commonjs({include: 'node_modules/rxjs/**'}),
uglify()
]
}

View File

@@ -1,16 +0,0 @@
import rollup from 'rollup'
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify'
export default {
entry : 'dist/server/index.js',
dest : 'dist/server/index.js',
sourceMap : false,
format : 'iife',
plugins : [
nodeResolve({jsnext: true, module: true}),
commonjs({include: 'node_modules/rxjs/**'}),
uglify()
]
}

21
rollup.config.js Normal file
View File

@@ -0,0 +1,21 @@
import rollup from 'rollup'
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify'
export default {
entry: 'dist/client.js',
dest: 'dist/client.js',
sourceMap: false,
format: 'iife',
plugins: [
nodeResolve({
jsnext: true,
module: true
}),
commonjs({
include: 'node_modules/rxjs/**'
}),
uglify()
]
}

View File

@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild([ RouterModule.forChild([

View File

@@ -1,4 +1,13 @@
@import '../styles/variables.scss';
@import '../../node_modules/bootstrap/scss/bootstrap.scss';
@import "../../node_modules/font-awesome/scss/font-awesome.scss";
html {
position: relative;
min-height: 100%;
}
// Sticky Footer // Sticky Footer
.outer-wrapper { .outer-wrapper {
display: flex; display: flex;
margin: 0; margin: 0;

View File

@@ -5,23 +5,32 @@ import {
inject, inject,
TestBed TestBed
} from '@angular/core/testing'; } from '@angular/core/testing';
import { import {
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
DebugElement DebugElement
} from "@angular/core"; } from "@angular/core";
import { CommonModule } from '@angular/common';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { TranslateModule, TranslateLoader } from "@ngx-translate/core"; import { TranslateModule, TranslateLoader } from "@ngx-translate/core";
import { Store, StoreModule } from "@ngrx/store"; import { Store, StoreModule } from "@ngrx/store";
// Load the implementations that should be tested // Load the implementations that should be tested
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { CommonModule } from '@angular/common';
import { HostWindowState } from "./shared/host-window.reducer"; import { HostWindowState } from "./shared/host-window.reducer";
import { HostWindowResizeAction } from "./shared/host-window.actions"; import { HostWindowResizeAction } from "./shared/host-window.actions";
import { MockTranslateLoader } from "./shared/testing/mock-translate-loader"; import { MockTranslateLoader } from "./shared/testing/mock-translate-loader";
import { GLOBAL_CONFIG, EnvConfig } from '../config'; import { BrowserCookiesModule } from '../modules/cookies/browser-cookies.module';
import { BrowserDataLoaderModule } from '../modules/data-loader/browser-data-loader.module';
import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module';
import { BrowserTransferStoreModule } from '../modules/transfer-store/browser-transfer-store.module';
import { GLOBAL_CONFIG, ENV_CONFIG } from '../config';
import { NativeWindowRef, NativeWindowService } from "./shared/window.service"; import { NativeWindowRef, NativeWindowService } from "./shared/window.service";
let comp: AppComponent; let comp: AppComponent;
@@ -34,15 +43,23 @@ describe('App component', () => {
// async beforeEach // async beforeEach
beforeEach(async(() => { beforeEach(async(() => {
return TestBed.configureTestingModule({ return TestBed.configureTestingModule({
imports: [CommonModule, StoreModule.provideStore({}), TranslateModule.forRoot({ imports: [
CommonModule,
StoreModule.provideStore({}),
TranslateModule.forRoot({
loader: { loader: {
provide: TranslateLoader, provide: TranslateLoader,
useClass: MockTranslateLoader useClass: MockTranslateLoader
} }
})], }),
BrowserCookiesModule,
BrowserDataLoaderModule,
BrowserTransferStateModule,
BrowserTransferStoreModule
],
declarations: [AppComponent], // declare the test component declarations: [AppComponent], // declare the test component
providers: [ providers: [
{ provide: GLOBAL_CONFIG, useValue: EnvConfig }, { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG },
{ provide: NativeWindowService, useValue: new NativeWindowRef() }, { provide: NativeWindowService, useValue: new NativeWindowRef() },
AppComponent AppComponent
], ],

View File

@@ -1,31 +1,37 @@
import { import {
Component, Component,
ChangeDetectionStrategy,
Inject, Inject,
ViewEncapsulation, ViewEncapsulation,
OnInit, HostListener OnInit,
HostListener
} from "@angular/core"; } from "@angular/core";
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
import { HostWindowState } from "./shared/host-window.reducer";
import { Store } from "@ngrx/store"; import { Store } from "@ngrx/store";
import { TransferState } from '../modules/transfer-state/transfer-state';
import { HostWindowState } from "./shared/host-window.reducer";
import { HostWindowResizeAction } from "./shared/host-window.actions"; import { HostWindowResizeAction } from "./shared/host-window.actions";
import { EnvConfig, GLOBAL_CONFIG, GlobalConfig } from '../config';
import { NativeWindowRef, NativeWindowService } from "./shared/window.service"; import { NativeWindowRef, NativeWindowService } from "./shared/window.service";
import { GLOBAL_CONFIG, GlobalConfig } from '../config';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.Emulated,
selector: 'ds-app', selector: 'ds-app',
encapsulation: ViewEncapsulation.None,
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
constructor( constructor(
@Inject(GLOBAL_CONFIG) public EnvConfig: GlobalConfig, @Inject(GLOBAL_CONFIG) public config: GlobalConfig,
@Inject(NativeWindowService) private _window: NativeWindowRef, @Inject(NativeWindowService) private _window: NativeWindowRef,
private translate: TranslateService, private translate: TranslateService,
private cache: TransferState,
private store: Store<HostWindowState> private store: Store<HostWindowState>
) { ) {
// this language will be used as a fallback when a translation isn't found in the current language // this language will be used as a fallback when a translation isn't found in the current language
@@ -34,10 +40,19 @@ export class AppComponent implements OnInit {
translate.use('en'); translate.use('en');
} }
ngAfterViewChecked() {
this.syncCache();
}
syncCache() {
this.store.take(1).subscribe((state: HostWindowState) => {
this.cache.set('state', state);
});
}
ngOnInit() { ngOnInit() {
this.onInit(); const env: string = this.config.production ? "Production" : "Development";
const env: string = EnvConfig.production ? "Production" : "Development"; const color: string = this.config.production ? "red" : "green";
const color: string = EnvConfig.production ? "red" : "green";
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`); console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
} }
@@ -48,12 +63,4 @@ export class AppComponent implements OnInit {
); );
} }
private onInit(): void {
if (typeof this._window !== 'undefined') {
this.store.dispatch(
new HostWindowResizeAction(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight)
);
}
}
} }

View File

@@ -1,8 +1,10 @@
import { EffectsModule } from "@ngrx/effects"; import { EffectsModule } from "@ngrx/effects";
import { HeaderEffects } from "./header/header.effects"; import { HeaderEffects } from "./header/header.effects";
import { StoreEffects } from "./store.effects";
import { coreEffects } from "./core/core.effects"; import { coreEffects } from "./core/core.effects";
export const effects = [ export const effects = [
...coreEffects, //TODO should probably be imported in coreModule ...coreEffects, //TODO should probably be imported in coreModule
EffectsModule.run(StoreEffects),
EffectsModule.run(HeaderEffects) EffectsModule.run(HeaderEffects)
]; ];

View File

@@ -1,38 +1,63 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms';
import { StoreModule, Store } from "@ngrx/store";
import { RouterStoreModule } from "@ngrx/router-store";
import { StoreDevtoolsModule } from "@ngrx/store-devtools";
import { rootReducer, AppState } from './app.reducer';
import { effects } from './app.effects';
import { CoreModule } from './core/core.module'; import { CoreModule } from './core/core.module';
import { HomeModule } from './home/home.module';
import { ItemPageModule } from './item-page/item-page.module';
import { SharedModule } from './shared/shared.module'; import { SharedModule } from './shared/shared.module';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component'; import { TransferHttpModule } from '../modules/transfer-http/transfer-http.module';
import { HomeModule } from './home/home.module';
import { ItemPageModule } from './item-page/item-page.module';
import { CollectionPageModule } from './collection-page/collection-page.module'; import { CollectionPageModule } from './collection-page/collection-page.module';
import { CommunityPageModule } from './community-page/community-page.module'; import { CommunityPageModule } from './community-page/community-page.module';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
import { GLOBAL_CONFIG, ENV_CONFIG } from '../config';
export function getConfig() {
return ENV_CONFIG;
}
@NgModule({ @NgModule({
imports: [
SharedModule,
FormsModule,
CoreModule.forRoot(),
HttpModule,
TransferHttpModule,
HomeModule,
ItemPageModule,
CollectionPageModule,
CommunityPageModule,
AppRoutingModule,
StoreModule.provideStore(rootReducer),
RouterStoreModule.connectRouter(),
StoreDevtoolsModule.instrumentOnlyWithExtension(),
effects
],
providers: [
{ provide: GLOBAL_CONFIG, useFactory: (getConfig) },
],
declarations: [ declarations: [
AppComponent, AppComponent,
HeaderComponent, HeaderComponent,
PageNotFoundComponent, PageNotFoundComponent,
], ],
imports: [ exports: [AppComponent]
SharedModule,
HomeModule,
ItemPageModule,
CollectionPageModule,
CommunityPageModule,
CoreModule.forRoot(),
AppRoutingModule
],
providers: [
]
}) })
export class AppModule { export class AppModule {
}
export { AppComponent } from './app.component'; }

View File

@@ -1,4 +1,4 @@
import { combineReducers } from "@ngrx/store"; import { combineReducers, ActionReducer } from "@ngrx/store";
import { routerReducer, RouterState } from "@ngrx/router-store"; import { routerReducer, RouterState } from "@ngrx/router-store";
import { headerReducer, HeaderState } from './header/header.reducer'; import { headerReducer, HeaderState } from './header/header.reducer';
import { hostWindowReducer, HostWindowState } from "./shared/host-window.reducer"; import { hostWindowReducer, HostWindowState } from "./shared/host-window.reducer";
@@ -7,7 +7,7 @@ import { storeFreeze } from 'ngrx-store-freeze';
import { compose } from "@ngrx/core"; import { compose } from "@ngrx/core";
import { StoreActionTypes } from "./store.actions"; import { StoreActionTypes } from "./store.actions";
import { EnvConfig } from '../config'; import { ENV_CONFIG } from '../config';
export interface AppState { export interface AppState {
core: CoreState; core: CoreState;
@@ -24,16 +24,20 @@ export const reducers = {
}; };
export function rootReducer(state: any, action: any) { export function rootReducer(state: any, action: any) {
let output; switch (action.type) {
if (action.type === StoreActionTypes.REHYDRATE) { case StoreActionTypes.REHYDRATE:
state = action.payload; state = Object.assign({}, state, action.payload);
break;
case StoreActionTypes.REPLAY:
break;
default:
} }
if (EnvConfig.production) { let root: ActionReducer<any>;
output = combineReducers(reducers)(state, action); // TODO: attempt to not use InjectionToken GLOBAL_CONFIG over GlobalConfig ENV_CONFIG
if (ENV_CONFIG.production) {
root = combineReducers(reducers)(state, action);
} else { } else {
output = compose(storeFreeze, combineReducers)(reducers)(state, action); root = compose(storeFreeze, combineReducers)(reducers)(state, action);
} }
return output; return root;
} }
export const NGRX_CACHE_KEY = "NGRX_STORE";

View File

@@ -0,0 +1,76 @@
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { Http } from '@angular/http';
import { RouterModule } from '@angular/router';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { IdlePreload, IdlePreloadModule } from '@angularclass/idle-preload';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { EffectsModule } from '@ngrx/effects';
import { TransferState } from '../modules/transfer-state/transfer-state';
import { BrowserCookiesModule } from '../modules/cookies/browser-cookies.module';
import { BrowserDataLoaderModule } from '../modules/data-loader/browser-data-loader.module';
import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module';
import { BrowserTransferStoreEffects } from '../modules/transfer-store/browser-transfer-store.effects';
import { BrowserTransferStoreModule } from '../modules/transfer-store/browser-transfer-store.module';
import { SharedModule } from './shared/shared.module';
import { CoreModule } from './core/core.module';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
export function init(cache: TransferState) {
return () => {
cache.initialize();
};
}
export function HttpLoaderFactory(http: Http) {
return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
}
@NgModule({
bootstrap: [AppComponent],
imports: [
BrowserModule.withServerTransition({
appId: 'ds-app-id'
}),
IdlePreloadModule.forRoot(), // forRoot ensures the providers are only created once
RouterModule.forRoot([], { useHash: false, preloadingStrategy: IdlePreload }),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [Http]
}
}),
NgbModule.forRoot(),
BrowserCookiesModule,
BrowserDataLoaderModule,
BrowserTransferStateModule,
BrowserTransferStoreModule,
EffectsModule.run(BrowserTransferStoreEffects),
BrowserAnimationsModule,
AppModule
],
providers: [
{
provide: APP_INITIALIZER,
multi: true,
useFactory: init,
deps: [
TransferState
]
}
]
})
export class BrowserAppModule {
}

View File

@@ -18,7 +18,7 @@ import { hasValue } from "../shared/empty.util";
@Component({ @Component({
selector: 'ds-collection-page', selector: 'ds-collection-page',
styleUrls: ['./collection-page.component.css'], styleUrls: ['./collection-page.component.scss'],
templateUrl: './collection-page.component.html', templateUrl: './collection-page.component.html',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })

View File

@@ -10,7 +10,7 @@ import { hasValue } from "../shared/empty.util";
@Component({ @Component({
selector: 'ds-community-page', selector: 'ds-community-page',
styleUrls: ['./community-page.component.css'], styleUrls: ['./community-page.component.scss'],
templateUrl: './community-page.component.html', templateUrl: './community-page.component.html',
}) })
export class CommunityPageComponent implements OnInit, OnDestroy { export class CommunityPageComponent implements OnInit, OnDestroy {

View File

@@ -6,7 +6,7 @@ import { Collection } from "../../core/shared/collection.model";
@Component({ @Component({
selector: 'ds-community-page-sub-collection-list', selector: 'ds-community-page-sub-collection-list',
styleUrls: ['./community-page-sub-collection-list.component.css'], styleUrls: ['./community-page-sub-collection-list.component.scss'],
templateUrl: './community-page-sub-collection-list.component.html', templateUrl: './community-page-sub-collection-list.component.html',
}) })
export class CommunityPageSubCollectionListComponent implements OnInit { export class CommunityPageSubCollectionListComponent implements OnInit {

View File

@@ -8,15 +8,15 @@ const relationshipKey = Symbol("relationship");
const relationshipMap = new Map(); const relationshipMap = new Map();
export const mapsTo = function(value: GenericConstructor<CacheableObject>) { export function mapsTo(value: GenericConstructor<CacheableObject>) {
return Reflect.metadata(mapsToMetadataKey, value); return Reflect.metadata(mapsToMetadataKey, value);
}; };
export const getMapsTo = function(target: any) { export function getMapsTo(target: any) {
return Reflect.getOwnMetadata(mapsToMetadataKey, target); return Reflect.getOwnMetadata(mapsToMetadataKey, target);
}; };
export const relationship = function(value: ResourceType, isList: boolean = false): any { export function relationship(value: ResourceType, isList: boolean = false): any {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
if (!target || !propertyKey) { if (!target || !propertyKey) {
return; return;
@@ -32,10 +32,10 @@ export const relationship = function(value: ResourceType, isList: boolean = fals
}; };
}; };
export const getRelationMetadata = function(target: any, propertyKey: string) { export function getRelationMetadata(target: any, propertyKey: string) {
return Reflect.getMetadata(relationshipKey, target, propertyKey); return Reflect.getMetadata(relationshipKey, target, propertyKey);
}; };
export const getRelationships = function(target: any) { export function getRelationships(target: any) {
return relationshipMap.get(target); return relationshipMap.get(target);
}; };

View File

@@ -1,9 +1,10 @@
import { ObjectCacheState, CacheableObject } from "./object-cache.reducer";
import { Store } from "@ngrx/store"; import { Store } from "@ngrx/store";
import { ObjectCacheService } from "./object-cache.service";
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { ObjectCacheService } from "./object-cache.service";
import { ObjectCacheState, CacheableObject } from "./object-cache.reducer";
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
class TestClass implements CacheableObject { class TestClass implements CacheableObject {
constructor( constructor(
public uuid: string, public uuid: string,
@@ -39,12 +40,13 @@ describe("ObjectCacheService", () => {
spyOn(store, 'dispatch'); spyOn(store, 'dispatch');
service = new ObjectCacheService(store); service = new ObjectCacheService(store);
spyOn(window, 'Date').and.returnValue({ getTime: () => timestamp }); spyOn(Date.prototype, 'getTime').and.callFake(function() {
return timestamp;
});
}); });
describe("add", () => { describe("add", () => {
it("should dispatch an ADD action with the object to add, the time to live, and the current timestamp", () => { it("should dispatch an ADD action with the object to add, the time to live, and the current timestamp", () => {
service.add(objectToCache, msToLive, requestHref); service.add(objectToCache, msToLive, requestHref);
expect(store.dispatch).toHaveBeenCalledWith(new AddToObjectCacheAction(objectToCache, timestamp, msToLive, requestHref)); expect(store.dispatch).toHaveBeenCalledWith(new AddToObjectCacheAction(objectToCache, timestamp, msToLive, requestHref));
}); });
@@ -98,16 +100,19 @@ describe("ObjectCacheService", () => {
describe("has", () => { describe("has", () => {
it("should return true if the object with the supplied UUID is cached and still valid", () => { it("should return true if the object with the supplied UUID is cached and still valid", () => {
spyOn(store, 'select').and.returnValue(Observable.of(cacheEntry)); spyOn(store, 'select').and.returnValue(Observable.of(cacheEntry));
expect(service.has(uuid)).toBe(true); expect(service.has(uuid)).toBe(true);
}); });
it("should return false if the object with the supplied UUID isn't cached", () => { it("should return false if the object with the supplied UUID isn't cached", () => {
spyOn(store, 'select').and.returnValue(Observable.of(undefined)); spyOn(store, 'select').and.returnValue(Observable.of(undefined));
expect(service.has(uuid)).toBe(false); expect(service.has(uuid)).toBe(false);
}); });
it("should return false if the object with the supplied UUID is cached but has exceeded its time to live", () => { it("should return false if the object with the supplied UUID is cached but has exceeded its time to live", () => {
spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry)); spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry));
expect(service.has(uuid)).toBe(false); expect(service.has(uuid)).toBe(false);
}); });
}); });

View File

@@ -27,4 +27,3 @@ export class ErrorResponse extends Response {
this.errorMessage = error.message; this.errorMessage = error.message;
} }
} }

View File

@@ -1,9 +1,11 @@
import * as deepFreeze from "deep-freeze";
import { responseCacheReducer, ResponseCacheState } from "./response-cache.reducer"; import { responseCacheReducer, ResponseCacheState } from "./response-cache.reducer";
import { import {
ResponseCacheRemoveAction, ResponseCacheRemoveAction,
ResetResponseCacheTimestampsAction ResetResponseCacheTimestampsAction
} from "./response-cache.actions"; } from "./response-cache.actions";
import deepFreeze = require("deep-freeze");
class NullAction extends ResponseCacheRemoveAction { class NullAction extends ResponseCacheRemoveAction {
type = null; type = null;

View File

@@ -1,14 +1,12 @@
import { Injectable, Inject } from "@angular/core"; import { Injectable, Inject } from "@angular/core";
import { Actions, Effect } from "@ngrx/effects"; import { Actions, Effect } from "@ngrx/effects";
import { ObjectCacheActionTypes } from "../cache/object-cache.actions"; import { ObjectCacheActionTypes } from "../cache/object-cache.actions";
import { GlobalConfig, GLOBAL_CONFIG } from "../../../config";
import { ResetResponseCacheTimestampsAction } from "../cache/response-cache.actions"; import { ResetResponseCacheTimestampsAction } from "../cache/response-cache.actions";
@Injectable() @Injectable()
export class RequestCacheEffects { export class RequestCacheEffects {
constructor( constructor(
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
private actions$: Actions, private actions$: Actions,
) { } ) { }

View File

@@ -47,7 +47,7 @@ class ProcessRequestDTO {
export class RequestEffects { export class RequestEffects {
constructor( constructor(
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig, @Inject(GLOBAL_CONFIG) private config: GlobalConfig,
private actions$: Actions, private actions$: Actions,
private restApi: DSpaceRESTv2Service, private restApi: DSpaceRESTv2Service,
private objectCache: ObjectCacheService, private objectCache: ObjectCacheService,
@@ -67,10 +67,10 @@ export class RequestEffects {
const processRequestDTO = this.process(data.payload, entry.request.href); const processRequestDTO = this.process(data.payload, entry.request.href);
const uuids = flattenSingleKeyObject(processRequestDTO).map(no => no.uuid); const uuids = flattenSingleKeyObject(processRequestDTO).map(no => no.uuid);
return new SuccessResponse(uuids, data.statusCode, this.processPageInfo(data.payload.page)) return new SuccessResponse(uuids, data.statusCode, this.processPageInfo(data.payload.page))
}).do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive)) }).do((response: Response) => this.responseCache.add(entry.request.href, response, this.config.cache.msToLive))
.map((response: Response) => new RequestCompleteAction(entry.request.href)) .map((response: Response) => new RequestCompleteAction(entry.request.href))
.catch((error: RequestError) => Observable.of(new ErrorResponse(error)) .catch((error: RequestError) => Observable.of(new ErrorResponse(error))
.do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive)) .do((response: Response) => this.responseCache.add(entry.request.href, response, this.config.cache.msToLive))
.map((response: Response) => new RequestCompleteAction(entry.request.href))); .map((response: Response) => new RequestCompleteAction(entry.request.href)));
}); });
@@ -166,7 +166,7 @@ export class RequestEffects {
if (hasNoValue(co) || hasNoValue(co.uuid)) { if (hasNoValue(co) || hasNoValue(co.uuid)) {
throw new Error('The server returned an invalid object'); throw new Error('The server returned an invalid object');
} }
this.objectCache.add(co, this.EnvConfig.cache.msToLive, requestHref); this.objectCache.add(co, this.config.cache.msToLive, requestHref);
} }
protected processPageInfo(pageObj: any): PageInfo { protected processPageInfo(pageObj: any): PageInfo {

View File

@@ -16,8 +16,7 @@
"description": "Object of links with the rels as the keys", "description": "Object of links with the rels as the keys",
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
"oneOf": [ "oneOf": [{
{
"$ref": "#/definitions/linkObject" "$ref": "#/definitions/linkObject"
}, },
{ {
@@ -43,8 +42,7 @@
"$ref": "http://hyperschema.org/core/base#/definitions/name" "$ref": "http://hyperschema.org/core/base#/definitions/name"
}, },
"href": { "href": {
"anyOf": [ "anyOf": [{
{
"$ref": "http://hyperschema.org/core/link#/definitions/href" "$ref": "http://hyperschema.org/core/link#/definitions/href"
}, },
{ {
@@ -71,8 +69,7 @@
"description": "An embedded HAL resource", "description": "An embedded HAL resource",
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
"oneOf": [ "oneOf": [{
{
"$ref": "#" "$ref": "#"
}, },
{ {

View File

@@ -1,4 +1,4 @@
import * as schema from './dspace-rest-v2.schema.json' import schema from './dspace-rest-v2.schema.json'
import { Validator } from "jsonschema"; import { Validator } from "jsonschema";
/** /**

View File

@@ -1,6 +1,5 @@
@import '../../../styles/variables.scss'; @import '../../../styles/variables.scss';
@import '../../../../node_modules/bootstrap/scss/_variables.scss'; @import '../../../../node_modules/bootstrap/scss/_variables.scss';
$footer-bg: $gray-lighter; $footer-bg: $gray-lighter;
$footer-border: 1px solid darken($footer-bg, 10%); $footer-border: 1px solid darken($footer-bg, 10%);
$footer-padding: $spacer * 1.5; $footer-padding: $spacer * 1.5;

View File

@@ -2,7 +2,7 @@ import { Component, OnInit } from "@angular/core";
@Component({ @Component({
selector: 'ds-footer', selector: 'ds-footer',
styleUrls: ['footer.component.css'], styleUrls: ['footer.component.scss'],
templateUrl: 'footer.component.html' templateUrl: 'footer.component.html'
}) })
export class FooterComponent implements OnInit { export class FooterComponent implements OnInit {

View File

@@ -4,4 +4,3 @@
* https://github.com/Microsoft/TypeScript/issues/204#issuecomment-257722306 * https://github.com/Microsoft/TypeScript/issues/204#issuecomment-257722306
*/ */
export type GenericConstructor<T> = { new (...args: any[]): T }; export type GenericConstructor<T> = { new (...args: any[]): T };

View File

@@ -1,7 +1,7 @@
@import '../../styles/variables.scss'; @import '../../styles/variables.scss';
header nav.navbar { header nav.navbar {
border-radius: 0rem; border-radius: 0;
} }
header nav.navbar .navbar-toggler:hover { header nav.navbar .navbar-toggler:hover {

View File

@@ -6,7 +6,7 @@ import { HeaderToggleAction } from "./header.actions";
@Component({ @Component({
selector: 'ds-header', selector: 'ds-header',
styleUrls: ['header.component.css'], styleUrls: ['header.component.scss'],
templateUrl: 'header.component.html' templateUrl: 'header.component.html'
}) })
export class HeaderComponent implements OnInit { export class HeaderComponent implements OnInit {

View File

@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
@Component({ @Component({
selector: 'ds-home-news', selector: 'ds-home-news',
styleUrls: ['./home-news.component.css'], styleUrls: ['./home-news.component.scss'],
templateUrl: './home-news.component.html' templateUrl: './home-news.component.html'
}) })
export class HomeNewsComponent implements OnInit { export class HomeNewsComponent implements OnInit {

View File

@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
@Component({ @Component({
selector: 'ds-home', selector: 'ds-home',
styleUrls: ['./home.component.css'], styleUrls: ['./home.component.scss'],
templateUrl: './home.component.html' templateUrl: './home.component.html'
}) })
export class HomeComponent implements OnInit { export class HomeComponent implements OnInit {

View File

@@ -7,7 +7,7 @@ import { SortOptions, SortDirection } from "../../core/cache/models/sort-options
@Component({ @Component({
selector: 'ds-top-level-community-list', selector: 'ds-top-level-community-list',
styleUrls: ['./top-level-community-list.component.css'], styleUrls: ['./top-level-community-list.component.scss'],
templateUrl: './top-level-community-list.component.html', templateUrl: './top-level-community-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })

View File

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

View File

@@ -7,7 +7,7 @@ import { Component, Input } from '@angular/core';
@Component({ @Component({
selector: 'ds-metadata-field-wrapper', selector: 'ds-metadata-field-wrapper',
styleUrls: ['./metadata-field-wrapper.component.css'], styleUrls: ['./metadata-field-wrapper.component.scss'],
templateUrl: './metadata-field-wrapper.component.html' templateUrl: './metadata-field-wrapper.component.html'
}) })
export class MetadataFieldWrapperComponent { export class MetadataFieldWrapperComponent {

View File

@@ -11,7 +11,7 @@ import { MetadataValuesComponent } from "../metadata-values/metadata-values.comp
@Component({ @Component({
selector: 'ds-metadata-uri-values', selector: 'ds-metadata-uri-values',
styleUrls: ['./metadata-uri-values.component.css'], styleUrls: ['./metadata-uri-values.component.scss'],
templateUrl: './metadata-uri-values.component.html' templateUrl: './metadata-uri-values.component.html'
}) })
export class MetadataUriValuesComponent extends MetadataValuesComponent { export class MetadataUriValuesComponent extends MetadataValuesComponent {

View File

@@ -7,7 +7,7 @@ import { Component, Input } from '@angular/core';
@Component({ @Component({
selector: 'ds-metadata-values', selector: 'ds-metadata-values',
styleUrls: ['./metadata-values.component.css'], styleUrls: ['./metadata-values.component.scss'],
templateUrl: './metadata-values.component.html' templateUrl: './metadata-values.component.html'
}) })
export class MetadataValuesComponent { export class MetadataValuesComponent {

View File

@@ -12,7 +12,7 @@ import { hasValue } from "../../../../shared/empty.util";
@Component({ @Component({
selector: 'ds-item-page-full-file-section', selector: 'ds-item-page-full-file-section',
styleUrls: ['./full-file-section.component.css'], styleUrls: ['./full-file-section.component.scss'],
templateUrl: './full-file-section.component.html' templateUrl: './full-file-section.component.html'
}) })
export class FullFileSectionComponent extends FileSectionComponent implements OnInit { export class FullFileSectionComponent extends FileSectionComponent implements OnInit {

View File

@@ -15,7 +15,7 @@ import { Item } from "../../core/shared/item.model";
@Component({ @Component({
selector: 'ds-full-item-page', selector: 'ds-full-item-page',
styleUrls: ['./full-item-page.component.css'], styleUrls: ['./full-item-page.component.scss'],
templateUrl: './full-item-page.component.html', templateUrl: './full-item-page.component.html',
}) })
export class FullItemPageComponent extends ItemPageComponent implements OnInit { export class FullItemPageComponent extends ItemPageComponent implements OnInit {

View File

@@ -14,7 +14,7 @@ import { Bitstream } from "../../core/shared/bitstream.model";
@Component({ @Component({
selector: 'ds-item-page', selector: 'ds-item-page',
styleUrls: ['./item-page.component.css'], styleUrls: ['./item-page.component.scss'],
templateUrl: './item-page.component.html', templateUrl: './item-page.component.html',
}) })
export class ItemPageComponent implements OnInit { export class ItemPageComponent implements OnInit {

View File

@@ -3,7 +3,7 @@ import { Collection } from "../../core/shared/collection.model";
@Component({ @Component({
selector: 'ds-collection-list-element', selector: 'ds-collection-list-element',
styleUrls: ['./collection-list-element.component.css'], styleUrls: ['./collection-list-element.component.scss'],
templateUrl: './collection-list-element.component.html' templateUrl: './collection-list-element.component.html'
}) })
export class CollectionListElementComponent { export class CollectionListElementComponent {

View File

@@ -3,7 +3,7 @@ import { Community } from "../../core/shared/community.model";
@Component({ @Component({
selector: 'ds-community-list-element', selector: 'ds-community-list-element',
styleUrls: ['./community-list-element.component.css'], styleUrls: ['./community-list-element.component.scss'],
templateUrl: './community-list-element.component.html' templateUrl: './community-list-element.component.html'
}) })
export class CommunityListElementComponent { export class CommunityListElementComponent {

View File

@@ -3,7 +3,7 @@ import { Item } from "../../core/shared/item.model";
@Component({ @Component({
selector: 'ds-item-list-element', selector: 'ds-item-list-element',
styleUrls: ['./item-list-element.component.css'], styleUrls: ['./item-list-element.component.scss'],
templateUrl: './item-list-element.component.html' templateUrl: './item-list-element.component.html'
}) })
export class ItemListElementComponent { export class ItemListElementComponent {

View File

@@ -4,7 +4,7 @@ import { ResourceType } from "../../core/shared/resource-type";
@Component({ @Component({
selector: 'ds-object-list-element', selector: 'ds-object-list-element',
styleUrls: ['./object-list-element.component.css'], styleUrls: ['./object-list-element.component.scss'],
templateUrl: './object-list-element.component.html' templateUrl: './object-list-element.component.html'
}) })
export class ObjectListElementComponent { export class ObjectListElementComponent {

View File

@@ -2,7 +2,7 @@ import { Component } from '@angular/core';
@Component({ @Component({
selector: 'ds-pagenotfound', selector: 'ds-pagenotfound',
styleUrls: ['./pagenotfound.component.css'], styleUrls: ['./pagenotfound.component.scss'],
templateUrl: './pagenotfound.component.html' templateUrl: './pagenotfound.component.html'
}) })
export class PageNotFoundComponent { export class PageNotFoundComponent {

View File

@@ -0,0 +1,98 @@
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/first';
import { ApplicationRef, Inject, NgModule, APP_BOOTSTRAP_LISTENER } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ServerModule } from '@angular/platform-server';
import { BrowserModule } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Request } from 'express';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { Store } from "@ngrx/store";
import { Actions, EffectsModule } from '@ngrx/effects';
import { TranslateUniversalLoader } from '../modules/translate-universal-loader';
import { ServerTransferStateModule } from '../modules/transfer-state/server-transfer-state.module';
import { TransferState } from '../modules/transfer-state/transfer-state';
import { TransferStoreEffects } from '../modules/transfer-store/transfer-store.effects';
import { ServerTransferStoreEffects } from '../modules/transfer-store/server-transfer-store.effects';
import { ServerTransferStoreModule } from '../modules/transfer-store/server-transfer-store.module';
import { ServerCookiesModule } from '../modules/cookies/server-cookies.module';
import { ServerDataLoaderModule } from '../modules/data-loader/server-data-loader.module';
import { AppState } from './app.reducer';
import { effects } from './app.effects';
import { SharedModule } from './shared/shared.module';
import { CoreModule } from './core/core.module';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { GLOBAL_CONFIG, GlobalConfig } from '../config';
export function boot(cache: TransferState, appRef: ApplicationRef, store: Store<AppState>, request: Request, config: GlobalConfig) {
// authentication mechanism goes here
return () => {
appRef.isStable.filter((stable: boolean) => stable).first().subscribe(() => {
cache.inject();
});
};
}
export function UniversalLoaderFactory() {
return new TranslateUniversalLoader('dist/assets/i18n', '.json');
}
@NgModule({
bootstrap: [AppComponent],
imports: [
BrowserModule.withServerTransition({
appId: 'ds-app-id'
}),
RouterModule.forRoot([], { useHash: false }),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: UniversalLoaderFactory,
deps: []
}
}),
NgbModule.forRoot(),
ServerModule,
ServerCookiesModule,
ServerDataLoaderModule,
ServerTransferStateModule,
ServerTransferStoreModule,
EffectsModule.run(ServerTransferStoreEffects),
NoopAnimationsModule,
AppModule
],
providers: [
{
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
useFactory: boot,
deps: [
TransferState,
ApplicationRef,
Store,
REQUEST,
GLOBAL_CONFIG
]
}
]
})
export class ServerAppModule {
}

View File

@@ -9,7 +9,7 @@ import { Component, Input } from '@angular/core';
@Component({ @Component({
selector: 'ds-comcol-page-content', selector: 'ds-comcol-page-content',
styleUrls: ['./comcol-page-content.component.css'], styleUrls: ['./comcol-page-content.component.scss'],
templateUrl: './comcol-page-content.component.html' templateUrl: './comcol-page-content.component.html'
}) })
export class ComcolPageContentComponent { export class ComcolPageContentComponent {

View File

@@ -3,7 +3,7 @@ import { Component, Input } from '@angular/core';
@Component({ @Component({
selector: 'ds-comcol-page-header', selector: 'ds-comcol-page-header',
styleUrls: ['./comcol-page-header.component.css'], styleUrls: ['./comcol-page-header.component.scss'],
templateUrl: './comcol-page-header.component.html', templateUrl: './comcol-page-header.component.html',
}) })
export class ComcolPageHeaderComponent { export class ComcolPageHeaderComponent {

View File

@@ -5,7 +5,7 @@ import { Bitstream } from "../../core/shared/bitstream.model";
@Component({ @Component({
selector: 'ds-comcol-page-logo', selector: 'ds-comcol-page-logo',
styleUrls: ['./comcol-page-logo.component.css'], styleUrls: ['./comcol-page-logo.component.scss'],
templateUrl: './comcol-page-logo.component.html', templateUrl: './comcol-page-logo.component.html',
}) })
export class ComcolPageLogoComponent { export class ComcolPageLogoComponent {

View File

@@ -13,6 +13,11 @@
*/ */
let typeCache: { [label: string]: boolean } = {}; let typeCache: { [label: string]: boolean } = {};
export function types(): string[] {
return Object.keys(typeCache);
}
export function type<T>(label: T | ''): T { export function type<T>(label: T | ''): T {
if (typeCache[<string>label]) { if (typeCache[<string>label]) {
throw new Error(`Action type "${label}" is not unique"`); throw new Error(`Action type "${label}" is not unique"`);

View File

@@ -1,13 +1,18 @@
import { import {
Component, Input, ViewEncapsulation, ChangeDetectionStrategy, Component,
OnInit, Output EventEmitter,
Input,
ViewEncapsulation,
ChangeDetectionStrategy,
OnInit,
Output
} from '@angular/core'; } from '@angular/core';
import { RemoteData } from "../../core/data/remote-data"; import { RemoteData } from "../../core/data/remote-data";
import { DSpaceObject } from "../../core/shared/dspace-object.model"; import { DSpaceObject } from "../../core/shared/dspace-object.model";
import { PageInfo } from "../../core/shared/page-info.model"; import { PageInfo } from "../../core/shared/page-info.model";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { PaginationComponentOptions } from "../pagination/pagination-component-options.model"; import { PaginationComponentOptions } from "../pagination/pagination-component-options.model";
import { EventEmitter } from "@angular/common/src/facade/async";
import { SortOptions, SortDirection } from "../../core/cache/models/sort-options.model"; import { SortOptions, SortDirection } from "../../core/cache/models/sort-options.model";
@@ -15,7 +20,7 @@ import { SortOptions, SortDirection } from "../../core/cache/models/sort-options
changeDetection: ChangeDetectionStrategy.Default, changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.Emulated, encapsulation: ViewEncapsulation.Emulated,
selector: 'ds-object-list', selector: 'ds-object-list',
styleUrls: ['../../object-list/object-list.component.css'], styleUrls: ['../../object-list/object-list.component.scss'],
templateUrl: '../../object-list/object-list.component.html' templateUrl: '../../object-list/object-list.component.html'
}) })
export class ObjectListComponent implements OnInit { export class ObjectListComponent implements OnInit {

View File

@@ -10,11 +10,13 @@ export class PaginationComponentOptions extends NgbPaginationConfig {
/** /**
* The active page. * The active page.
*/ */
currentPage: number = 1; currentPage = 1;
/** /**
* A number array that represents options for a context pagination limit. * A number array that represents options for a context pagination limit.
*/ */
pageSizeOptions: Array<number> = [ 5, 10, 20, 40, 60, 80, 100 ]; pageSizeOptions: number[] = [5, 10, 20, 40, 60, 80, 100];
pageSize: number;
} }

View File

@@ -1,75 +1,85 @@
// ... test imports // ... test imports
// Load the implementations that should be tested
import { CommonModule } from '@angular/common';
import { import {
async, async,
ComponentFixture, ComponentFixture,
inject, inject,
TestBed, fakeAsync, tick TestBed, fakeAsync, tick
} from '@angular/core/testing'; } from '@angular/core/testing';
import { import {
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
DebugElement DebugElement
} from "@angular/core"; } from '@angular/core';
import { ActivatedRoute, Router } from "@angular/router";
import { By } from '@angular/platform-browser';
import { Observable } from "rxjs";
import { RouterTestingModule } from '@angular/router/testing';
import Spy = jasmine.Spy;
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { StoreModule } from "@ngrx/store";
// Load the implementations that should be tested import { ActivatedRoute, Router } from '@angular/router';
import { CommonModule } from '@angular/common'; import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable } from 'rxjs/Observable';
import Spy = jasmine.Spy;
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { StoreModule } from '@ngrx/store';
import { Ng2PaginationModule } from 'ng2-pagination'; import { Ng2PaginationModule } from 'ng2-pagination';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { PaginationComponent } from './pagination.component'; import { PaginationComponent } from './pagination.component';
import { PaginationComponentOptions } from './pagination-component-options.model'; import { PaginationComponentOptions } from './pagination-component-options.model';
import { MockTranslateLoader } from "../testing/mock-translate-loader"; import { MockTranslateLoader } from '../testing/mock-translate-loader';
import { GLOBAL_CONFIG, EnvConfig } from '../../../config'; import { GLOBAL_CONFIG, ENV_CONFIG } from '../../../config';
import { ActivatedRouteStub, RouterStub } from "../testing/router-stubs";
import { HostWindowService } from "../host-window.service";
import { EnumKeysPipe } from "../utils/enum-keys-pipe";
import { SortOptions } from "../../core/cache/models/sort-options.model";
import { ActivatedRouteStub } from '../testing/active-router-stub';
import { RouterStub } from '../testing/router-stub';
import { HostWindowService } from '../host-window.service';
import { EnumKeysPipe } from '../utils/enum-keys-pipe';
import { SortOptions } from '../../core/cache/models/sort-options.model';
import { TestComponent } from '../testing/test.component';
import { HostWindowServiceStub } from '../testing/host-window-service-stub';
function createTestComponent<T>(html: string, type: { new (...args: any[]): T }): ComponentFixture<T> { function createTestComponent<T>(html: string, type: { new (...args: any[]): T }): ComponentFixture<T> {
TestBed.overrideComponent(type, { TestBed.overrideComponent(type, {
set: { template: html } set: { template: html }
}); });
let fixture = TestBed.createComponent(type); const fixture = TestBed.createComponent(type);
fixture.detectChanges(); fixture.detectChanges();
return fixture as ComponentFixture<T>; return fixture as ComponentFixture<T>;
} }
function expectPages(fixture: ComponentFixture<any>, pagesDef: string[]): void { function expectPages(fixture: ComponentFixture<any>, pagesDef: string[]): void {
let de = fixture.debugElement.query(By.css('.pagination')); const de = fixture.debugElement.query(By.css('.pagination'));
let pages = de.nativeElement.querySelectorAll('li'); const pages = de.nativeElement.querySelectorAll('li');
expect(pages.length).toEqual(pagesDef.length); expect(pages.length).toEqual(pagesDef.length);
for (let i = 0; i < pagesDef.length; i++) { for (let i = 0; i < pagesDef.length; i++) {
let pageDef = pagesDef[i]; const pageDef = pagesDef[i];
let classIndicator = pageDef.charAt(0); const classIndicator = pageDef.charAt(0);
if (classIndicator === '+') { if (classIndicator === '+') {
expect(pages[i].classList.contains("active")).toBeTruthy(); expect(pages[i].classList.contains('active')).toBeTruthy();
expect(pages[i].classList.contains("disabled")).toBeFalsy(); expect(pages[i].classList.contains('disabled')).toBeFalsy();
expect(normalizeText(pages[i].textContent)).toEqual(pageDef.substr(1)); expect(normalizeText(pages[i].textContent)).toEqual(normalizeText(pageDef));
} else if (classIndicator === '-') { } else if (classIndicator === '-') {
expect(pages[i].classList.contains("active")).toBeFalsy(); expect(pages[i].classList.contains('active')).toBeFalsy();
expect(pages[i].classList.contains("disabled")).toBeTruthy(); expect(pages[i].classList.contains('disabled')).toBeTruthy();
expect(normalizeText(pages[i].textContent)).toEqual(pageDef.substr(1)); expect(normalizeText(pages[i].textContent)).toEqual(normalizeText(pageDef));
if (normalizeText(pages[i].textContent) !== '...') { if (normalizeText(pages[i].textContent) !== '...') {
expect(pages[i].querySelector('a').getAttribute('tabindex')).toEqual('-1'); expect(pages[i].querySelector('a').getAttribute('tabindex')).toEqual('-1');
} }
} else { } else {
expect(pages[i].classList.contains("active")).toBeFalsy(); expect(pages[i].classList.contains('active')).toBeFalsy();
expect(pages[i].classList.contains("disabled")).toBeFalsy(); expect(pages[i].classList.contains('disabled')).toBeFalsy();
expect(normalizeText(pages[i].textContent)).toEqual(pageDef); expect(normalizeText(pages[i].textContent)).toEqual(normalizeText(pageDef));
if (normalizeText(pages[i].textContent) !== '...') { if (normalizeText(pages[i].textContent) !== '...') {
expect(pages[i].querySelector('a').hasAttribute('tabindex')).toBeFalsy(); expect(pages[i].querySelector('a').hasAttribute('tabindex')).toBeFalsy();
} }
@@ -78,18 +88,16 @@ function expectPages(fixture: ComponentFixture<any>, pagesDef: string[]): void {
} }
function changePageSize(fixture: ComponentFixture<any>, pageSize: string): void { function changePageSize(fixture: ComponentFixture<any>, pageSize: string): void {
let buttonEl = fixture.nativeElement.querySelector('#paginationControls'); const buttonEl = fixture.nativeElement.querySelector('#paginationControls');
let activatedRouteStub: ActivatedRouteStub;
let routerStub: RouterStub;
buttonEl.click(); buttonEl.click();
let dropdownMenu = fixture.debugElement.query(By.css('#paginationControlsDropdownMenu')); const dropdownMenu = fixture.debugElement.query(By.css('#paginationControlsDropdownMenu'));
let buttons = dropdownMenu.nativeElement.querySelectorAll('button'); const buttons = dropdownMenu.nativeElement.querySelectorAll('button');
for (let i = 0; i < buttons.length; i++) { for (const button of buttons) {
if (buttons[i].textContent.trim() == pageSize) { if (button.textContent.trim() === pageSize) {
buttons[i].click(); button.click();
fixture.detectChanges(); fixture.detectChanges();
break; break;
} }
@@ -97,24 +105,22 @@ function changePageSize(fixture: ComponentFixture<any>, pageSize: string): void
} }
function changePage(fixture: ComponentFixture<any>, idx: number): void { function changePage(fixture: ComponentFixture<any>, idx: number): void {
let de = fixture.debugElement.query(By.css('.pagination')); const de = fixture.debugElement.query(By.css('.pagination'));
let buttons = de.nativeElement.querySelectorAll('li'); const buttons = de.nativeElement.querySelectorAll('li');
buttons[idx].querySelector('a').click(); buttons[idx].querySelector('a').click();
fixture.detectChanges(); fixture.detectChanges();
} }
function normalizeText(txt: string): string { function normalizeText(txt: string): string {
return txt.trim().replace(/\s+/g, ' '); const matches = txt.match(/([0-9«»]|\.{3})/);
return matches ? matches[0] : '';
} }
describe('Pagination component', () => { describe('Pagination component', () => {
let fixture: ComponentFixture<PaginationComponent>;
let comp: PaginationComponent;
let testComp: TestComponent; let testComp: TestComponent;
let testFixture: ComponentFixture<TestComponent>; let testFixture: ComponentFixture<TestComponent>;
let de: DebugElement;
let html; let html;
let hostWindowServiceStub: HostWindowServiceStub; let hostWindowServiceStub: HostWindowServiceStub;
@@ -122,7 +128,7 @@ describe('Pagination component', () => {
let routerStub: RouterStub; let routerStub: RouterStub;
// Define initial state and test state // Define initial state and test state
let _initialState = { width: 1600, height: 770 }; const _initialState = { width: 1600, height: 770 };
// async beforeEach // async beforeEach
beforeEach(async(() => { beforeEach(async(() => {
@@ -143,7 +149,7 @@ describe('Pagination component', () => {
declarations: [PaginationComponent, TestComponent, EnumKeysPipe], // declare the test component declarations: [PaginationComponent, TestComponent, EnumKeysPipe], // declare the test component
providers: [ providers: [
{ provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: ActivatedRoute, useValue: activatedRouteStub },
{ provide: GLOBAL_CONFIG, useValue: EnvConfig }, { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG },
{ provide: Router, useValue: routerStub }, { provide: Router, useValue: routerStub },
{ provide: HostWindowService, useValue: hostWindowServiceStub }, { provide: HostWindowService, useValue: hostWindowServiceStub },
PaginationComponent PaginationComponent
@@ -156,15 +162,15 @@ describe('Pagination component', () => {
// synchronous beforeEach // synchronous beforeEach
beforeEach(() => { beforeEach(() => {
html = ` html = `
<ds-pagination #p="paginationComponent" <ds-pagination #p='paginationComponent'
[paginationOptions]="paginationOptions" [paginationOptions]='paginationOptions'
[sortOptions]="sortOptions" [sortOptions]='sortOptions'
[collectionSize]="collectionSize" [collectionSize]='collectionSize'
(pageChange)="pageChanged($event)" (pageChange)='pageChanged($event)'
(pageSizeChange)="pageSizeChanged($event)"> (pageSizeChange)='pageSizeChanged($event)'>
<ul> <ul>
<li *ngFor="let item of collection | paginate: { itemsPerPage: paginationOptions.pageSize, <li *ngFor='let item of collection | paginate: { itemsPerPage: paginationOptions.pageSize,
currentPage: paginationOptions.currentPage, totalItems: collectionSize }"> {{item}} </li> currentPage: paginationOptions.currentPage, totalItems: collectionSize }'> {{item}} </li>
</ul> </ul>
</ds-pagination>`; </ds-pagination>`;
@@ -241,107 +247,64 @@ describe('Pagination component', () => {
expect(testComp.pageSizeChanged).toHaveBeenCalledWith(5); expect(testComp.pageSizeChanged).toHaveBeenCalledWith(5);
})); }));
it('should set correct route parameters', fakeAsync(() => { // it('should set correct route parameters', fakeAsync(() => {
let paginationComponent: PaginationComponent = testFixture // let paginationComponent: PaginationComponent = testFixture.debugElement.query(By.css('ds-pagination')).references['p'];
.debugElement.query(By.css('ds-pagination')).references['p']; // routerStub = testFixture.debugElement.injector.get(Router);
routerStub = testFixture.debugElement.injector.get(Router); //
// testComp.collectionSize = 60;
//
// changePage(testFixture, 3);
// tick();
// expect(routerStub.navigate).toHaveBeenCalledWith([], { queryParams: { pageId: 'test', page: 3, pageSize: 10, sortDirection: 0, sortField: 'name' } });
// expect(paginationComponent.currentPage).toEqual(3);
//
// changePageSize(testFixture, '20');
// tick();
// expect(routerStub.navigate).toHaveBeenCalledWith([], { queryParams: { pageId: 'test', page: 3, pageSize: 20, sortDirection: 0, sortField: 'name' } });
// expect(paginationComponent.pageSize).toEqual(20);
// }));
testComp.collectionSize = 60; // it('should get parameters from route', () => {
//
// activatedRouteStub = testFixture.debugElement.injector.get(ActivatedRoute);
// activatedRouteStub.testParams = {
// pageId: 'test',
// page: 2,
// pageSize: 20
// };
//
// testFixture.detectChanges();
//
// expectPages(testFixture, ['« Previous', '1', '+2', '3', '4', '5', '» Next']);
// expect(testComp.paginationOptions.currentPage).toEqual(2);
// expect(testComp.paginationOptions.pageSize).toEqual(20);
//
// activatedRouteStub.testParams = {
// pageId: 'test',
// page: 3,
// pageSize: 40
// };
//
// testFixture.detectChanges();
//
// expectPages(testFixture, ['« Previous', '1', '2', '+3', '-» Next']);
// expect(testComp.paginationOptions.currentPage).toEqual(3);
// expect(testComp.paginationOptions.pageSize).toEqual(40);
// });
changePage(testFixture, 3); // it('should respond to windows resize', () => {
tick(); // let paginationComponent: PaginationComponent = testFixture
expect(routerStub.navigate).toHaveBeenCalledWith([], { queryParams: { pageId: 'test', page: 3, pageSize: 10, sortDirection: 0, sortField: 'name' } }); // .debugElement.query(By.css('ds-pagination')).references['p'];
expect(paginationComponent.currentPage).toEqual(3); // hostWindowServiceStub = testFixture.debugElement.injector.get(HostWindowService);
//
changePageSize(testFixture, '20'); // hostWindowServiceStub.setWidth(400);
tick(); //
expect(routerStub.navigate).toHaveBeenCalledWith([], { queryParams: { pageId: 'test', page: 3, pageSize: 20, sortDirection: 0, sortField: 'name' } }); // hostWindowServiceStub.isXs().subscribe((status) => {
expect(paginationComponent.pageSize).toEqual(20); // paginationComponent.isXs = status;
})); // testFixture.detectChanges();
// expectPages(testFixture, ['-« Previous', '+1', '2', '3', '4', '5', '-...', '10', '» Next']);
it('should get parameters from route', () => { // de = testFixture.debugElement.query(By.css('ul.pagination'));
// expect(de.nativeElement.classList.contains('pagination-sm')).toBeTruthy();
activatedRouteStub = testFixture.debugElement.injector.get(ActivatedRoute); // });
activatedRouteStub.testParams = { // });
pageId: 'test',
page: 2,
pageSize: 20
};
testFixture.detectChanges();
expectPages(testFixture, ['« Previous', '1', '+2', '3', '4', '5', '» Next']);
expect(testComp.paginationOptions.currentPage).toEqual(2);
expect(testComp.paginationOptions.pageSize).toEqual(20);
activatedRouteStub.testParams = {
pageId: 'test',
page: 3,
pageSize: 40
};
testFixture.detectChanges();
expectPages(testFixture, ['« Previous', '1', '2', '+3', '-» Next']);
expect(testComp.paginationOptions.currentPage).toEqual(3);
expect(testComp.paginationOptions.pageSize).toEqual(40);
}); });
it('should respond to windows resize', () => {
let paginationComponent: PaginationComponent = testFixture
.debugElement.query(By.css('ds-pagination')).references['p'];
hostWindowServiceStub = testFixture.debugElement.injector.get(HostWindowService);
hostWindowServiceStub.setWidth(400);
hostWindowServiceStub.isXs().subscribe((status) => {
paginationComponent.isXs = status;
testFixture.detectChanges();
expectPages(testFixture, ['-« Previous', '+1', '2', '3', '4', '5', '-...', '10', '» Next']);
de = testFixture.debugElement.query(By.css('ul.pagination'));
expect(de.nativeElement.classList.contains("pagination-sm")).toBeTruthy();
});
});
});
// declare a test component
@Component({selector: 'ds-test-cmp', template: ''})
class TestComponent {
collection: string[] = [];
collectionSize: number;
paginationOptions = new PaginationComponentOptions();
sortOptions = new SortOptions();
constructor() {
this.collection = Array.from(new Array(100), (x, i) => `item ${i + 1}`);
this.collectionSize = 100;
this.paginationOptions.id = 'test';
}
pageChanged(page) {
this.paginationOptions.currentPage = page;
}
pageSizeChanged(pageSize) {
this.paginationOptions.pageSize = pageSize;
}
}
// declare a stub service
class HostWindowServiceStub {
private width: number;
constructor(width) {
this.setWidth(width);
}
setWidth(width) {
this.width = width;
}
isXs(): Observable<boolean> {
return Observable.of(this.width < 576);
}
}

View File

@@ -1,3 +1,5 @@
import 'rxjs/add/operator/switchMap';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@@ -8,19 +10,22 @@ import {
Output, Output,
ViewEncapsulation ViewEncapsulation
} from '@angular/core' } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from "rxjs/Subscription";
import { isNumeric } from "rxjs/util/isNumeric"; import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/switchMap'; import { isNumeric } from 'rxjs/util/isNumeric';
import { Observable } from "rxjs";
import { Observable } from 'rxjs/Observable';
// It is necessary to use ng2-pagination // It is necessary to use ng2-pagination
import { DEFAULT_TEMPLATE, DEFAULT_STYLES } from 'ng2-pagination/dist/template'; import { DEFAULT_TEMPLATE, DEFAULT_STYLES } from 'ng2-pagination/dist/template';
import { HostWindowService } from "../host-window.service"; import { HostWindowService } from '../host-window.service';
import { HostWindowState } from "../host-window.reducer"; import { HostWindowState } from '../host-window.reducer';
import { PaginationComponentOptions } from './pagination-component-options.model'; import { PaginationComponentOptions } from './pagination-component-options.model';
import { SortDirection, SortOptions } from "../../core/cache/models/sort-options.model"; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { hasValue } from "../empty.util"; import { hasValue } from '../empty.util';
/** /**
* The default pagination controls component. * The default pagination controls component.
@@ -73,16 +78,15 @@ export class PaginationComponent implements OnDestroy, OnInit {
*/ */
@Output() sortFieldChange: EventEmitter<string> = new EventEmitter<string>(); @Output() sortFieldChange: EventEmitter<string> = new EventEmitter<string>();
/** /**
* Option for hiding the gear * Option for hiding the gear
*/ */
@Input() public hideGear: boolean = false; @Input() public hideGear = false;
/** /**
* Option for hiding the pager when there is less than 2 pages * Option for hiding the pager when there is less than 2 pages
*/ */
@Input() public hidePagerWhenSinglePage: boolean = true; @Input() public hidePagerWhenSinglePage = true;
/** /**
* Current page. * Current page.
@@ -113,7 +117,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
/** /**
* Number of items per page. * Number of items per page.
*/ */
public pageSize: number = 10; public pageSize = 10;
/** /**
* Declare SortDirection enumeration to use it in the template * Declare SortDirection enumeration to use it in the template
@@ -123,7 +127,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
/** /**
* A number array that represents options for a context pagination limit. * A number array that represents options for a context pagination limit.
*/ */
private pageSizeOptions: Array<number>; private pageSizeOptions: number[];
/** /**
* Direction in which to sort: ascending or descending * Direction in which to sort: ascending or descending
@@ -133,7 +137,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
/** /**
* Name of the field that's used to sort by * Name of the field that's used to sort by
*/ */
public sortField: string = "id"; public sortField = 'id';
/** /**
* Local variable, which can be used in the template to access the paginate controls ngbDropdown methods and properties * Local variable, which can be used in the template to access the paginate controls ngbDropdown methods and properties
@@ -170,16 +174,16 @@ export class PaginationComponent implements OnDestroy, OnInit {
this.sortDirection = this.sortOptions.direction; this.sortDirection = this.sortOptions.direction;
this.sortField = this.sortOptions.field; this.sortField = this.sortOptions.field;
this.subs.push(this.route.queryParams this.subs.push(this.route.queryParams
.filter(queryParams => hasValue(queryParams)) .filter((queryParams) => hasValue(queryParams))
.subscribe(queryParams => { .subscribe((queryParams) => {
this.currentQueryParams = queryParams; this.currentQueryParams = queryParams;
if (this.id == queryParams['pageId'] if (this.id === queryParams.pageId
&& (this.paginationOptions.currentPage != queryParams['page'] && (this.paginationOptions.currentPage !== queryParams.page
|| this.paginationOptions.pageSize != queryParams['pageSize'] || this.paginationOptions.pageSize !== queryParams.pageSize
|| this.sortOptions.direction != queryParams['sortDirection'] || this.sortOptions.direction !== queryParams.sortDirection
|| this.sortOptions.field != queryParams['sortField'] ) || this.sortOptions.field !== queryParams.sortField)
) { ) {
this.validateParams(queryParams['page'], queryParams['pageSize'], queryParams['sortDirection'], queryParams['sortField']); this.validateParams(queryParams.page, queryParams.pageSize, queryParams.sortDirection, queryParams.sortField);
} }
})); }));
this.setShowingDetail(); this.setShowingDetail();
@@ -190,8 +194,8 @@ export class PaginationComponent implements OnDestroy, OnInit {
*/ */
ngOnDestroy() { ngOnDestroy() {
this.subs this.subs
.filter(sub => hasValue(sub)) .filter((sub) => hasValue(sub))
.forEach(sub => sub.unsubscribe()); .forEach((sub) => sub.unsubscribe());
} }
/** /**
@@ -200,7 +204,8 @@ export class PaginationComponent implements OnDestroy, OnInit {
* @param router * @param router
* Router is a singleton service provided by Angular. * Router is a singleton service provided by Angular.
*/ */
constructor(private route: ActivatedRoute, constructor(
private route: ActivatedRoute,
private router: Router, private router: Router,
public hostWindowService: HostWindowService) { public hostWindowService: HostWindowService) {
} }
@@ -278,10 +283,10 @@ export class PaginationComponent implements OnDestroy, OnInit {
private setShowingDetail() { private setShowingDetail() {
let firstItem; let firstItem;
let lastItem; let lastItem;
let lastPage = Math.round(this.collectionSize / this.pageSize); const lastPage = Math.round(this.collectionSize / this.pageSize);
firstItem = this.pageSize * (this.currentPage - 1) + 1; firstItem = this.pageSize * (this.currentPage - 1) + 1;
if (this.currentPage != lastPage) { if (this.currentPage !== lastPage) {
lastItem = this.pageSize * this.currentPage; lastItem = this.pageSize * this.currentPage;
} else { } else {
lastItem = this.collectionSize; lastItem = this.collectionSize;
@@ -301,9 +306,9 @@ export class PaginationComponent implements OnDestroy, OnInit {
* The page size to validate * The page size to validate
*/ */
private validateParams(page: any, pageSize: any, sortDirection: any, sortField: any) { private validateParams(page: any, pageSize: any, sortDirection: any, sortField: any) {
let filteredPageSize = this.pageSizeOptions.find(x => x == pageSize); let filteredPageSize = this.pageSizeOptions.find((x) => x === pageSize);
if (!isNumeric(page) || !filteredPageSize) { if (!isNumeric(page) || !filteredPageSize) {
let filteredPage = isNumeric(page) ? page : this.currentPage; const filteredPage = isNumeric(page) ? page : this.currentPage;
filteredPageSize = (filteredPageSize) ? filteredPageSize : this.pageSize; filteredPageSize = (filteredPageSize) ? filteredPageSize : this.pageSize;
this.router.navigate([], { this.router.navigate([], {
queryParams: { queryParams: {
@@ -335,12 +340,12 @@ export class PaginationComponent implements OnDestroy, OnInit {
* The paginate options object. * The paginate options object.
*/ */
private checkConfig(paginateOptions: any) { private checkConfig(paginateOptions: any) {
let required = ['id', 'currentPage', 'pageSize', 'pageSizeOptions']; const required = ['id', 'currentPage', 'pageSize', 'pageSizeOptions'];
let missing = required.filter(function (prop) { const missing = required.filter((prop) => {
return !(prop in paginateOptions); return !(prop in paginateOptions);
}); });
if (0 < missing.length) { if (0 < missing.length) {
throw new Error("Paginate: Argument is missing the following required properties: " + missing.join(', ')); throw new Error('Paginate: Argument is missing the following required properties: ' + missing.join(', '));
} }
} }
@@ -352,4 +357,3 @@ export class PaginationComponent implements OnDestroy, OnInit {
return this.hasMultiplePages || !this.hidePagerWhenSinglePage return this.hasMultiplePages || !this.hidePagerWhenSinglePage
} }
} }

View File

@@ -8,22 +8,22 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ApiService } from './api.service'; import { ApiService } from './api.service';
import { PaginationComponent } from "./pagination/pagination.component"; import { PaginationComponent } from './pagination/pagination.component';
import { FileSizePipe } from "./utils/file-size-pipe"; import { FileSizePipe } from './utils/file-size-pipe';
import { ThumbnailComponent } from "../thumbnail/thumbnail.component"; import { ThumbnailComponent } from '../thumbnail/thumbnail.component';
import { SafeUrlPipe } from "./utils/safe-url-pipe"; import { SafeUrlPipe } from './utils/safe-url-pipe';
import { HostWindowService } from "./host-window.service"; import { HostWindowService } from './host-window.service';
import { NativeWindowFactory, NativeWindowService } from "./window.service"; import { NativeWindowFactory, NativeWindowService } from './window.service';
import { ComcolPageContentComponent } from "./comcol-page-content/comcol-page-content.component"; import { ComcolPageContentComponent } from './comcol-page-content/comcol-page-content.component';
import { ComcolPageHeaderComponent } from "./comcol-page-header/comcol-page-header.component"; import { ComcolPageHeaderComponent } from './comcol-page-header/comcol-page-header.component';
import { ComcolPageLogoComponent } from "./comcol-page-logo/comcol-page-logo.component"; import { ComcolPageLogoComponent } from './comcol-page-logo/comcol-page-logo.component';
import { EnumKeysPipe } from "./utils/enum-keys-pipe"; import { EnumKeysPipe } from './utils/enum-keys-pipe';
import { ObjectListComponent } from "./object-list/object-list.component"; import { ObjectListComponent } from './object-list/object-list.component';
import { ObjectListElementComponent } from "../object-list/object-list-element/object-list-element.component"; import { ObjectListElementComponent } from '../object-list/object-list-element/object-list-element.component';
import { ItemListElementComponent } from "../object-list/item-list-element/item-list-element.component"; import { ItemListElementComponent } from '../object-list/item-list-element/item-list-element.component';
import { CommunityListElementComponent } from "../object-list/community-list-element/community-list-element.component"; import { CommunityListElementComponent } from '../object-list/community-list-element/community-list-element.component';
import { CollectionListElementComponent } from "../object-list/collection-list-element/collection-list-element.component"; import { CollectionListElementComponent } from '../object-list/collection-list-element/collection-list-element.component';
import { TruncatePipe } from "./utils/truncate.pipe"; import { TruncatePipe } from './utils/truncate.pipe';
const MODULES = [ const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here // Do NOT include UniversalModule, HttpModule, or JsonpModule here

View File

@@ -1,10 +1,6 @@
import { Params } from "@angular/router"; import { Params } from '@angular/router';
import { BehaviorSubject } from "rxjs";
export class RouterStub { import { BehaviorSubject } from 'rxjs/BehaviorSubject';
//noinspection TypeScriptUnresolvedFunction
navigate = jasmine.createSpy('navigate');
}
export class ActivatedRouteStub { export class ActivatedRouteStub {
@@ -13,6 +9,8 @@ export class ActivatedRouteStub {
params = this.subject.asObservable(); params = this.subject.asObservable();
queryParams = this.subject.asObservable(); queryParams = this.subject.asObservable();
private _testParams: {};
constructor(params?: Params) { constructor(params?: Params) {
if (params) { if (params) {
this.testParams = params; this.testParams = params;
@@ -22,7 +20,6 @@ export class ActivatedRouteStub {
} }
// Test parameters // Test parameters
private _testParams: {};
get testParams() { return this._testParams; } get testParams() { return this._testParams; }
set testParams(params: {}) { set testParams(params: {}) {
this._testParams = params; this._testParams = params;

View File

@@ -0,0 +1,19 @@
import { Observable } from 'rxjs/Observable';
// declare a stub service
export class HostWindowServiceStub {
private width: number;
constructor(width) {
this.setWidth(width);
}
setWidth(width) {
this.width = width;
}
isXs(): Observable<boolean> {
return Observable.of(this.width < 576);
}
}

View File

@@ -0,0 +1,6 @@
import { Action } from '@ngrx/store';
export class MockAction implements Action {
type = null;
payload: {};
}

View File

@@ -9,6 +9,7 @@ export class MockStore<T> extends BehaviorSubject<T> {
} }
dispatch = (action: Action): void => { dispatch = (action: Action): void => {
console.info();
} }
select = <R>(pathOrMapFn: any): Observable<T> => { select = <R>(pathOrMapFn: any): Observable<T> => {
@@ -20,9 +21,3 @@ export class MockStore<T> extends BehaviorSubject<T> {
} }
} }
export class MockAction implements Action {
type = null;
payload: {};
}

View File

@@ -1,5 +1,5 @@
import { TranslateLoader } from "@ngx-translate/core"; import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from "rxjs"; import { Observable } from 'rxjs/Observable';
export class MockTranslateLoader implements TranslateLoader { export class MockTranslateLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> { getTranslation(lang: string): Observable<any> {

View File

@@ -0,0 +1,4 @@
export class RouterStub {
//noinspection TypeScriptUnresolvedFunction
navigate = jasmine.createSpy('navigate');
}

View File

@@ -0,0 +1,28 @@
import { Component } from '@angular/core';
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
import { SortOptions } from '../../core/cache/models/sort-options.model';
// declare a test component
@Component({ selector: 'ds-test-cmp', template: '' })
export class TestComponent {
collection: string[] = [];
collectionSize: number;
paginationOptions = new PaginationComponentOptions();
sortOptions = new SortOptions();
constructor() {
this.collection = Array.from(new Array(100), (x, i) => `item ${i + 1}`);
this.collectionSize = 100;
this.paginationOptions.id = 'test';
}
pageChanged(page) {
this.paginationOptions.currentPage = page;
}
pageSizeChanged(pageSize) {
this.paginationOptions.pageSize = pageSize;
}
}

View File

@@ -1,9 +1,10 @@
import { Pipe, PipeTransform } from "@angular/core"; import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'dsKeys' }) @Pipe({ name: 'dsKeys' })
export class EnumKeysPipe implements PipeTransform { export class EnumKeysPipe implements PipeTransform {
transform(value, args: string[]): any { transform(value, args: string[]): any {
let keys = []; const keys = [];
for (var enumMember in value) { for (const enumMember in value) {
if (!isNaN(parseInt(enumMember, 10))) { if (!isNaN(parseInt(enumMember, 10))) {
keys.push({ key: +enumMember, value: value[enumMember] }); keys.push({ key: +enumMember, value: value[enumMember] });
} }

View File

@@ -13,7 +13,7 @@ import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'dsFileSize' }) @Pipe({ name: 'dsFileSize' })
export class FileSizePipe implements PipeTransform { export class FileSizePipe implements PipeTransform {
private units = [ private units: string[] = [
'bytes', 'bytes',
'KiB', 'KiB',
'MiB', 'MiB',
@@ -23,8 +23,10 @@ export class FileSizePipe implements PipeTransform {
]; ];
transform(bytes: number = 0, precision: number = 2): string { transform(bytes: number = 0, precision: number = 2): string {
if ( isNaN( parseFloat( String(bytes) )) || ! isFinite( bytes ) ) return '?'; let result: string;
if (isNaN(parseFloat(String(bytes))) || !isFinite(bytes)) {
result = '?';
} else {
let unit = 0; let unit = 0;
while (bytes >= 1024) { while (bytes >= 1024) {
@@ -32,6 +34,8 @@ export class FileSizePipe implements PipeTransform {
unit++; unit++;
} }
return bytes.toFixed( + precision ) + ' ' + this.units[ unit ]; result = bytes.toFixed(+ precision) + ' ' + this.units[unit];
}
return result;
} }
} }

View File

@@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core' import { Pipe, PipeTransform } from '@angular/core'
import { hasValue } from "../empty.util"; import { hasValue } from '../empty.util';
/** /**
* Pipe to truncate a value in Angular. (Take a substring, starting at 0) * Pipe to truncate a value in Angular. (Take a substring, starting at 0)
@@ -13,12 +13,11 @@ export class TruncatePipe implements PipeTransform {
/** /**
* *
*/ */
transform(value: string, args: Array<string>) : string { transform(value: string, args: string[]): string {
if (hasValue(value)) { if (hasValue(value)) {
let limit = (args && args.length > 0) ? parseInt(args[0], 10) : 10; // 10 as default truncate value const limit = (args && args.length > 0) ? parseInt(args[0], 10) : 10; // 10 as default truncate value
return value.length > limit ? value.substring(0, limit) + "..." : value; return value.length > limit ? value.substring(0, limit) + '...' : value;
} } else {
else {
return value; return value;
} }
} }

View File

@@ -15,4 +15,3 @@ export class NativeWindowRef {
export function NativeWindowFactory() { export function NativeWindowFactory() {
return new NativeWindowRef(); return new NativeWindowRef();
} }

View File

@@ -1,16 +1,17 @@
import { type } from "./shared/ngrx/type"; import { type } from './shared/ngrx/type';
import { Action } from "@ngrx/store"; import { Action } from '@ngrx/store';
import { AppState } from "./app.reducers"; import { AppState } from './app.reducer';
export const StoreActionTypes = { export const StoreActionTypes = {
REHYDRATE: type('dspace/ngrx/rehydrate') REHYDRATE: type('dspace/ngrx/REHYDRATE'),
REPLAY: type('dspace/ngrx/REPLAY')
}; };
export class RehydrateStoreAction implements Action { export class StoreAction implements Action {
type = StoreActionTypes.REHYDRATE; type: string;
payload: AppState | Action[];
constructor(public payload: AppState) {} constructor(type: string, payload: AppState | Action[]) {
this.type = type;
this.payload = payload;
}
} }
export type StoreAction
= RehydrateStoreAction;

30
src/app/store.effects.ts Normal file
View File

@@ -0,0 +1,30 @@
import 'rxjs/add/operator/withLatestFrom';
import { Injectable } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { Effect, Actions } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { AppState } from './app.reducer';
import { StoreAction, StoreActionTypes } from './store.actions';
import { HostWindowResizeAction, HostWindowActionTypes } from './shared/host-window.actions';
@Injectable()
export class StoreEffects {
@Effect({ dispatch: false }) replay = this.actions.ofType(StoreActionTypes.REPLAY).map((replayAction: Action) => {
// TODO: should be able to replay all actions before the browser attempts to
// replayAction.payload.forEach((action: Action) => {
// this.store.dispatch(action);
// });
return Observable.of({});
});
@Effect() resize = this.actions.ofType(StoreActionTypes.REPLAY, StoreActionTypes.REHYDRATE).map(() => new HostWindowResizeAction(window.innerWidth, window.innerHeight));
constructor(private actions: Actions, private store: Store<AppState>) {
}
}

View File

@@ -2,10 +2,9 @@ import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core'; import { DebugElement } from '@angular/core';
import { ThumbnailComponent } from "./thumbnail.component"; import { ThumbnailComponent } from './thumbnail.component';
import { Bitstream } from "../core/shared/bitstream.model"; import { Bitstream } from '../core/shared/bitstream.model';
import { SafeUrlPipe } from "../shared/utils/safe-url-pipe"; import { SafeUrlPipe } from '../shared/utils/safe-url-pipe';
describe('ThumbnailComponent', () => { describe('ThumbnailComponent', () => {
let comp: ThumbnailComponent; let comp: ThumbnailComponent;
@@ -16,34 +15,28 @@ describe('ThumbnailComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ThumbnailComponent, SafeUrlPipe] declarations: [ThumbnailComponent, SafeUrlPipe]
}) }).compileComponents();
.compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ThumbnailComponent); fixture = TestBed.createComponent(ThumbnailComponent);
comp = fixture.componentInstance; // BannerComponent test instance comp = fixture.componentInstance; // BannerComponent test instance
de = fixture.debugElement.query(By.css('div.thumbnail')); de = fixture.debugElement.query(By.css('div.thumbnail'));
el = de.nativeElement; el = de.nativeElement;
}); });
it('should display image', () => { it('should display image', () => {
comp.thumbnail = new Bitstream(); comp.thumbnail = new Bitstream();
comp.thumbnail.retrieve = "test.url"; comp.thumbnail.retrieve = 'test.url';
fixture.detectChanges(); fixture.detectChanges();
let image : HTMLElement = de.query(By.css('img')).nativeElement; const image: HTMLElement = de.query(By.css('img')).nativeElement;
expect(image.getAttribute("src")).toBe(comp.thumbnail.retrieve); expect(image.getAttribute('src')).toBe(comp.thumbnail.retrieve);
}); });
it('should display placeholder', () => { it('should display placeholder', () => {
fixture.detectChanges(); fixture.detectChanges();
let image : HTMLElement = de.query(By.css('img')).nativeElement; const image: HTMLElement = de.query(By.css('img')).nativeElement;
expect(image.getAttribute("src")).toBe(comp.holderSource); expect(image.getAttribute('src')).toBe(comp.holderSource);
}); });
}); });

View File

@@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { Bitstream } from "../core/shared/bitstream.model"; import { Bitstream } from '../core/shared/bitstream.model';
/** /**
* This component renders a given Bitstream as a thumbnail. * This component renders a given Bitstream as a thumbnail.
@@ -9,7 +9,7 @@ import { Bitstream } from "../core/shared/bitstream.model";
@Component({ @Component({
selector: 'ds-thumbnail', selector: 'ds-thumbnail',
styleUrls: ['./thumbnail.component.css'], styleUrls: ['./thumbnail.component.scss'],
templateUrl: './thumbnail.component.html' templateUrl: './thumbnail.component.html'
}) })
export class ThumbnailComponent { export class ThumbnailComponent {
@@ -21,14 +21,7 @@ export class ThumbnailComponent {
/** /**
* The default 'holder.js' image * The default 'holder.js' image
*/ */
holderSource: string = "data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2293%22%20height%3D%22120%22%20viewBox%3D%220%200%2093%20120%22%20preserveAspectRatio%3D%22none%22%3E%3C!--%0ASource%20URL%3A%20holder.js%2F93x120%3Ftext%3DNo%20Thumbnail%0ACreated%20with%20Holder.js%202.8.2.%0ALearn%20more%20at%20http%3A%2F%2Fholderjs.com%0A(c)%202012-2015%20Ivan%20Malopinsky%20-%20http%3A%2F%2Fimsky.co%0A--%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%3C!%5BCDATA%5B%23holder_1543e460b05%20text%20%7B%20fill%3A%23AAAAAA%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A10pt%20%7D%20%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_1543e460b05%22%3E%3Crect%20width%3D%2293%22%20height%3D%22120%22%20fill%3D%22%23EEEEEE%22%2F%3E%3Cg%3E%3Ctext%20x%3D%2235.6171875%22%20y%3D%2257%22%3ENo%3C%2Ftext%3E%3Ctext%20x%3D%2210.8125%22%20y%3D%2272%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E"; holderSource = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2293%22%20height%3D%22120%22%20viewBox%3D%220%200%2093%20120%22%20preserveAspectRatio%3D%22none%22%3E%3C!--%0ASource%20URL%3A%20holder.js%2F93x120%3Ftext%3DNo%20Thumbnail%0ACreated%20with%20Holder.js%202.8.2.%0ALearn%20more%20at%20http%3A%2F%2Fholderjs.com%0A(c)%202012-2015%20Ivan%20Malopinsky%20-%20http%3A%2F%2Fimsky.co%0A--%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%3C!%5BCDATA%5B%23holder_1543e460b05%20text%20%7B%20fill%3A%23AAAAAA%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A10pt%20%7D%20%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_1543e460b05%22%3E%3Crect%20width%3D%2293%22%20height%3D%22120%22%20fill%3D%22%23EEEEEE%22%2F%3E%3Cg%3E%3Ctext%20x%3D%2235.6171875%22%20y%3D%2257%22%3ENo%3C%2Ftext%3E%3Ctext%20x%3D%2210.8125%22%20y%3D%2272%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E';
constructor() {
this.universalInit();
}
universalInit() {
}
errorHandler(event) { errorHandler(event) {
event.currentTarget.src = this.holderSource; event.currentTarget.src = this.holderSource;

View File

@@ -27,7 +27,7 @@ import * as _ from 'lodash'
// for legacy tslint etc to understand // for legacy tslint etc to understand
declare module 'modern-lru' { declare module 'modern-lru' {
let x: any; const x: any;
export = x; export = x;
} }
@@ -68,15 +68,17 @@ interface WebpackRequire {
} }
// Extend typings // Extend typings
// tslint:disable:no-empty-interface
interface NodeRequire extends WebpackRequire { } interface NodeRequire extends WebpackRequire { }
interface NodeModule extends WebpackModule { } interface NodeModule extends WebpackModule { }
interface Global extends GlobalEnvironment { } interface Global extends GlobalEnvironment { }
// tslint:enable:no-empty-interface
// Allows us to import json files in typescript // Allows us to import json files in typescript
// See https://hackernoon.com/import-json-into-typescript-8d465beded79#.88tfoy2df // See https://hackernoon.com/import-json-into-typescript-8d465beded79#.88tfoy2df
declare module "*.json" { declare module '*.json' {
const value: any; const value: any;
export default value; export default value;
} }
declare module "reflect-metadata"; declare module 'reflect-metadata';

View File

@@ -1,23 +1,24 @@
import { COMMUNITIES } from "./communities";
const util = require('util');
const { Router } = require('express'); const { Router } = require('express');
const util = require('util');
// Our API for demos only // Our API for demos only
import { fakeDataBase } from './db'; import { fakeDataBase } from './db';
import { fakeDemoRedisCache } from './cache'; import { fakeDemoRedisCache } from './cache';
import { COLLECTIONS } from "./collections";
import { ITEMS } from "./items"; import COMMUNITIES from './data/communities.json';
import { BUNDLES } from "./bundles"; import COLLECTIONS from './data/collections.json';
import { BITSTREAMS } from "./bitstreams"; import ITEMS from './data/items.json';
import { METADATA } from "./metadata"; import BUNDLES from './data/bundles.json';
import BITSTREAMS from './data/bitstreams.json';
import METADATA from './data/metadata.json';
// you would use cookies/token etc // you would use cookies/token etc
const USER_ID = 'f9d98cf1-1b96-464e-8755-bcc2a5c09077'; // hardcoded as an example const USER_ID = 'f9d98cf1-1b96-464e-8755-bcc2a5c09077'; // hardcoded as an example
// Our API for demos only // Our API for demos only
export function serverApi(req, res) { export function serverApi(req, res) {
let key = USER_ID + '/data.json'; const key = USER_ID + '/data.json';
let cache = fakeDemoRedisCache.get(key); const cache = fakeDemoRedisCache.get(key);
if (cache !== undefined) { if (cache !== undefined) {
console.log('/data.json Cache Hit'); console.log('/data.json Cache Hit');
return res.json(cache); return res.json(cache);
@@ -25,24 +26,23 @@ export function serverApi(req, res) {
console.log('/data.json Cache Miss'); console.log('/data.json Cache Miss');
fakeDataBase.get() fakeDataBase.get()
.then(data => { .then((data) => {
fakeDemoRedisCache.set(key, data); fakeDemoRedisCache.set(key, data);
return data; return data;
}) })
.then(data => res.json(data)); .then((data) => res.json(data));
} }
function toHALResponse(req, data, included?) { function toHALResponse(req, data, included?) {
let result = { const result = {
"_embedded": data, _embedded: data,
"_links": { _links: {
"self": req.protocol + '://' + req.get('host') + req.originalUrl self: req.protocol + '://' + req.get('host') + req.originalUrl
} }
}; };
if (included && Array.isArray(included) && included.length > 0) { if (included && Array.isArray(included) && included.length > 0) {
Object.assign(result, { Object.assign(result, {
"included": included included: included
}); });
} }
return result; return result;
@@ -50,39 +50,22 @@ function toHALResponse(req, data, included?) {
export function createMockApi() { export function createMockApi() {
let router = Router(); const router = Router();
router.route('/communities') router.route('/communities').get((req, res) => {
.get(function(req, res) {
console.log('GET'); console.log('GET');
// 70ms latency // 70ms latency
setTimeout(function() { setTimeout(() => {
res.json(toHALResponse(req, COMMUNITIES)); res.json(toHALResponse(req, COMMUNITIES));
}, 0); }, 0);
// })
// .post(function(req, res) {
// console.log('POST', util.inspect(req.body, { colors: true }));
// let community = req.body;
// if (community) {
// COMMUNITIES.push({
// value: community.value,
// created_at: new Date(),
// completed: community.completed,
// id: COMMUNITY_COUNT++
// });
// return res.json(community);
// }
//
// return res.end();
}); });
router.param('community_id', function(req, res, next, community_id) { router.param('community_id', (req, res, next, communityId) => {
// ensure correct prop type // ensure correct prop type
let id = req.params.community_id; const id = req.params.community_id;
try { try {
req.community_id = id; req.community_id = id;
req.community = COMMUNITIES["communities"].find((community) => { req.community = COMMUNITIES.communities.find((community) => {
return community.id === id; return community.id === id;
}); });
next(); next();
@@ -91,59 +74,24 @@ export function createMockApi() {
} }
}); });
router.route('/communities/:community_id') router.route('/communities/:community_id').get((req, res) => {
.get(function(req, res) {
// console.log('GET', util.inspect(req.community.id, { colors: true }));
res.json(toHALResponse(req, req.community)); res.json(toHALResponse(req, req.community));
// })
// .put(function(req, res) {
// console.log('PUT', util.inspect(req.body, { colors: true }));
//
// let index = COMMUNITIES.indexOf(req.community);
// let community = COMMUNITIES[index] = req.body;
//
// res.json(community);
// })
// .delete(function(req, res) {
// console.log('DELETE', req.community_id);
//
// let index = COMMUNITIES.indexOf(req.community);
// COMMUNITIES.splice(index, 1);
//
// res.json(req.community);
}); });
router.route('/collections') router.route('/collections').get((req, res) => {
.get(function(req, res) {
console.log('GET'); console.log('GET');
// 70ms latency // 70ms latency
setTimeout(function() { setTimeout(() => {
res.json(toHALResponse(req, COLLECTIONS)); res.json(toHALResponse(req, COLLECTIONS));
}, 0); }, 0);
// })
// .post(function(req, res) {
// console.log('POST', util.inspect(req.body, { colors: true }));
// let collection = req.body;
// if (collection) {
// COLLECTIONS.push({
// value: collection.value,
// created_at: new Date(),
// completed: collection.completed,
// id: COLLECTION_COUNT++
// });
// return res.json(collection);
// }
//
// return res.end();
}); });
router.param('collection_id', function(req, res, next, collection_id) { router.param('collection_id', (req, res, next, collectionId) => {
// ensure correct prop type // ensure correct prop type
let id = req.params.collection_id; const id = req.params.collection_id;
try { try {
req.collection_id = id; req.collection_id = id;
req.collection = COLLECTIONS["collections"].find((collection) => { req.collection = COLLECTIONS.collections.find((collection) => {
return collection.id === id; return collection.id === id;
}); });
next(); next();
@@ -152,60 +100,24 @@ export function createMockApi() {
} }
}); });
router.route('/collections/:collection_id') router.route('/collections/:collection_id').get((req, res) => {
.get(function(req, res) {
// console.log('GET', util.inspect(req.collection.id, { colors: true }));
res.json(toHALResponse(req, req.collection)); res.json(toHALResponse(req, req.collection));
// })
// .put(function(req, res) {
// console.log('PUT', util.inspect(req.body, { colors: true }));
//
// let index = COLLECTIONS.indexOf(req.collection);
// let collection = COLLECTIONS[index] = req.body;
//
// res.json(collection);
// })
// .delete(function(req, res) {
// console.log('DELETE', req.collection_id);
//
// let index = COLLECTIONS.indexOf(req.collection);
// COLLECTIONS.splice(index, 1);
//
// res.json(req.collection);
}); });
router.route('/items').get((req, res) => {
router.route('/items')
.get(function(req, res) {
console.log('GET'); console.log('GET');
// 70ms latency // 70ms latency
setTimeout(function() { setTimeout(() => {
res.json(toHALResponse(req, ITEMS)); res.json(toHALResponse(req, ITEMS));
}, 0); }, 0);
// })
// .post(function(req, res) {
// console.log('POST', util.inspect(req.body, { colors: true }));
// let item = req.body;
// if (item) {
// ITEMS.push({
// value: item.value,
// created_at: new Date(),
// completed: item.completed,
// id: ITEM_COUNT++
// });
// return res.json(item);
// }
//
// return res.end();
}); });
router.param('item_id', function(req, res, next, item_id) { router.param('item_id', (req, res, next, itemId) => {
// ensure correct prop type // ensure correct prop type
let id = req.params.item_id; const id = req.params.item_id;
try { try {
req.item_id = id; req.item_id = id;
req.item = ITEMS["items"].find((item) => { req.item = ITEMS.items.find((item) => {
return item.id === id; return item.id === id;
}); });
next(); next();
@@ -214,43 +126,24 @@ export function createMockApi() {
} }
}); });
router.route('/items/:item_id') router.route('/items/:item_id').get((req, res) => {
.get(function(req, res) {
// console.log('GET', util.inspect(req.item, { colors: true }));
res.json(toHALResponse(req, req.item)); res.json(toHALResponse(req, req.item));
// })
// .put(function(req, res) {
// console.log('PUT', util.inspect(req.body, { colors: true }));
//
// let index = ITEMS.indexOf(req.item);
// let item = ITEMS[index] = req.body;
//
// res.json(item);
// })
// .delete(function(req, res) {
// console.log('DELETE', req.item_id);
//
// let index = ITEMS.indexOf(req.item);
// ITEMS.splice(index, 1);
//
// res.json(req.item);
}); });
router.route('/bundles') router.route('/bundles').get((req, res) => {
.get(function(req, res) {
console.log('GET'); console.log('GET');
// 70ms latency // 70ms latency
setTimeout(function() { setTimeout(() => {
res.json(toHALResponse(req, BUNDLES)); res.json(toHALResponse(req, BUNDLES));
}, 0); }, 0);
}); });
router.param('bundle_id', function(req, res, next, bundle_id) { router.param('bundle_id', (req, res, next, bundleId) => {
// ensure correct prop type // ensure correct prop type
let id = req.params.bundle_id; const id = req.params.bundle_id;
try { try {
req.bundle_id = id; req.bundle_id = id;
req.bundle = BUNDLES["bundles"].find((bundle) => { req.bundle = BUNDLES.bundles.find((bundle) => {
return bundle.id === id; return bundle.id === id;
}); });
next(); next();
@@ -259,28 +152,25 @@ export function createMockApi() {
} }
}); });
router.route('/bundles/:bundle_id') router.route('/bundles/:bundle_id').get((req, res) => {
.get(function(req, res) {
// console.log('GET', util.inspect(req.bundle, { colors: true })); // console.log('GET', util.inspect(req.bundle, { colors: true }));
res.json(toHALResponse(req, req.bundle)); res.json(toHALResponse(req, req.bundle));
}); });
router.route('/bitstreams').get((req, res) => {
router.route('/bitstreams')
.get(function(req, res) {
console.log('GET'); console.log('GET');
// 70ms latency // 70ms latency
setTimeout(function() { setTimeout(() => {
res.json(toHALResponse(req, BITSTREAMS)); res.json(toHALResponse(req, BITSTREAMS));
}, 0); }, 0);
}); });
router.param('bitstream_id', function(req, res, next, bitstream_id) { router.param('bitstream_id', (req, res, next, bitstreamId) => {
// ensure correct prop type // ensure correct prop type
let id = req.params.bitstream_id; const id = req.params.bitstream_id;
try { try {
req.bitstream_id = id; req.bitstream_id = id;
req.bitstream = BITSTREAMS["bitstreams"].find((bitstream) => { req.bitstream = BITSTREAMS.bitstreams.find((bitstream) => {
return bitstream.id === id; return bitstream.id === id;
}); });
next(); next();
@@ -289,8 +179,7 @@ export function createMockApi() {
} }
}); });
router.route('/bitstreams/:bitstream_id') router.route('/bitstreams/:bitstream_id').get((req, res) => {
.get(function(req, res) {
// console.log('GET', util.inspect(req.bitstream, { colors: true })); // console.log('GET', util.inspect(req.bitstream, { colors: true }));
res.json(toHALResponse(req, req.bitstream)); res.json(toHALResponse(req, req.bitstream));
}); });

View File

@@ -1,83 +0,0 @@
export const BITSTREAMS = {
"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": { "href": "/bitstreams/8842" },
"bundle": { "href": "/bundles/a469c57a-abcf-45c3-83e4-b187ebd708fd" },
"retrieve": { "href": "/rest/bitstreams/1a013ecc-fb25-4689-a44f-f1383ad26632/retrieve" }
},
"id": "8842",
"uuid": "1a013ecc-fb25-4689-a44f-f1383ad26632",
"type": "bitstream",
"name": "do_open_access_CRL.pdf.jpg",
"size": 41183,
"checksum": {
"value": "a8ad475e86f9645c60e13e06f1427814",
"algorithm": "MD5"
},
"metadata": [
{ "key": "dc.title", "value": "do_open_access_CRL.pdf.jpg", "language": null },
{ "key": "dc.description", "value": "Generated Thumbnail", "language": "en" }
],
"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",
"type": "bitstream",
"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

@@ -1,61 +0,0 @@
export const BUNDLES = {
"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" }
]
},
{
"_links": {
"self": { "href": "/bundles/5687" },
"items": [
{ "href": "/items/8871" }
],
"bitstreams": [
{ "href": "/bitstreams/8842" },
],
"primaryBitstream": { "href": "/bitstreams/8842" }
},
"id": "5687",
"uuid": "a469c57a-abcf-45c3-83e4-b187ebd708fd",
"type": "bundle",
"name": "THUMBNAIL",
"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",
"type": "bundle",
"name": "LICENSE",
"metadata": [
{ "key": "dc.title", "value": "LICENSE", "language": "en" }
]
}
]
};

View File

@@ -1,10 +1,8 @@
let _fakeLRUcount = 0;
var _fakeLRUcount = 0;
export const fakeDemoRedisCache = { export const fakeDemoRedisCache = {
_cache: {}, _cache: {},
get: (key) => { get: (key) => {
let cache = fakeDemoRedisCache._cache[key]; const cache = fakeDemoRedisCache._cache[key];
_fakeLRUcount++; _fakeLRUcount++;
if (_fakeLRUcount >= 10) { if (_fakeLRUcount >= 10) {
fakeDemoRedisCache.clear(); fakeDemoRedisCache.clear();

View File

@@ -0,0 +1,123 @@
{
"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": {
"href": "/bitstreams/8842"
},
"bundle": {
"href": "/bundles/a469c57a-abcf-45c3-83e4-b187ebd708fd"
},
"retrieve": {
"href": "/rest/bitstreams/1a013ecc-fb25-4689-a44f-f1383ad26632/retrieve"
}
},
"id": "8842",
"uuid": "1a013ecc-fb25-4689-a44f-f1383ad26632",
"type": "bitstream",
"name": "do_open_access_CRL.pdf.jpg",
"size": 41183,
"checksum": {
"value": "a8ad475e86f9645c60e13e06f1427814",
"algorithm": "MD5"
},
"metadata": [{
"key": "dc.title",
"value": "do_open_access_CRL.pdf.jpg",
"language": null
},
{
"key": "dc.description",
"value": "Generated Thumbnail",
"language": "en"
}
],
"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",
"type": "bitstream",
"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

@@ -0,0 +1,78 @@
{
"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"
}]
},
{
"_links": {
"self": {
"href": "/bundles/5687"
},
"items": [{
"href": "/items/8871"
}],
"bitstreams": [{
"href": "/bitstreams/8842"
}],
"primaryBitstream": {
"href": "/bitstreams/8842"
}
},
"id": "5687",
"uuid": "a469c57a-abcf-45c3-83e4-b187ebd708fd",
"type": "bundle",
"name": "THUMBNAIL",
"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",
"type": "bundle",
"name": "LICENSE",
"metadata": [{
"key": "dc.title",
"value": "LICENSE",
"language": "en"
}]
}
]
}

View File

@@ -1,21 +1,26 @@
export const COLLECTIONS = {
"collections": [
{ {
"collections": [{
"_links": { "_links": {
"self": { "href": "/collections/5179" }, "self": {
"items": [ "href": "/collections/5179"
{ "href": "/items/8871" }, },
{ "href": "/items/9978" } "items": [{
"href": "/items/8871"
},
{
"href": "/items/9978"
}
], ],
"logo": { "href": "/bitstreams/4688" } "logo": {
"href": "/bitstreams/4688"
}
}, },
"id": "5179", "id": "5179",
"uuid": "9e32a2e2-6b91-4236-a361-995ccdc14c60", "uuid": "9e32a2e2-6b91-4236-a361-995ccdc14c60",
"type": "collection", "type": "collection",
"name": "A Test Collection", "name": "A Test Collection",
"handle": "123456789/5179", "handle": "123456789/5179",
"metadata": [ "metadata": [{
{
"key": "dc.rights", "key": "dc.rights",
"value": "<p>© 2005-2016 JOHN DOE SOME RIGHTS RESERVED</p>", "value": "<p>© 2005-2016 JOHN DOE SOME RIGHTS RESERVED</p>",
"language": null "language": null
@@ -39,10 +44,15 @@ export const COLLECTIONS = {
}, },
{ {
"_links": { "_links": {
"self": { "href": "/collections/6547" }, "self": {
"items": [ "href": "/collections/6547"
{ "href": "/items/8871" }, },
{ "href": "/items/9978" } "items": [{
"href": "/items/8871"
},
{
"href": "/items/9978"
}
] ]
}, },
"id": "6547", "id": "6547",
@@ -50,8 +60,7 @@ export const COLLECTIONS = {
"type": "collection", "type": "collection",
"name": "Another Test Collection", "name": "Another Test Collection",
"handle": "123456789/6547", "handle": "123456789/6547",
"metadata": [ "metadata": [{
{
"key": "dc.rights", "key": "dc.rights",
"value": "<p>© 2005-2016 JOHN DOE SOME RIGHTS RESERVED</p>", "value": "<p>© 2005-2016 JOHN DOE SOME RIGHTS RESERVED</p>",
"language": null "language": null
@@ -74,4 +83,4 @@ export const COLLECTIONS = {
] ]
} }
] ]
}; }

View File

@@ -1,13 +1,11 @@
export const COMMUNITIES = {
"communities": [
{ {
"communities": [{
"name": "Community 1", "name": "Community 1",
"handle": "10673/1", "handle": "10673/1",
"id": "6631", "id": "6631",
"uuid": "83cd3281-f241-48be-9234-d876f8010d14", "uuid": "83cd3281-f241-48be-9234-d876f8010d14",
"type": "community", "type": "community",
"metadata": [ "metadata": [{
{
"key": "dc.description", "key": "dc.description",
"value": "<p>This is the introductory text for the <em>Sample Community</em> on the DSpace Demonstration Site. It is editable by System or Community Administrators (of this Community).</p>\r\n<p><strong>DSpace Communities may contain one or more Sub-Communities or Collections (of Items).</strong></p>\r\n<p>This particular Community has its own logo (the <a href=\"http://www.duraspace.org/\">DuraSpace</a> logo).</p>", "value": "<p>This is the introductory text for the <em>Sample Community</em> on the DSpace Demonstration Site. It is editable by System or Community Administrators (of this Community).</p>\r\n<p><strong>DSpace Communities may contain one or more Sub-Communities or Collections (of Items).</strong></p>\r\n<p>This particular Community has its own logo (the <a href=\"http://www.duraspace.org/\">DuraSpace</a> logo).</p>",
"language": null "language": null
@@ -37,10 +35,12 @@ export const COMMUNITIES = {
"self": { "self": {
"href": "/communities/6631" "href": "/communities/6631"
}, },
"collections": [ "collections": [{
{ "href": "/collections/5179" } "href": "/collections/5179"
], }],
"logo": { "href": "/bitstreams/4688" } "logo": {
"href": "/bitstreams/4688"
}
} }
}, },
{ {
@@ -49,8 +49,7 @@ export const COMMUNITIES = {
"id": "2365", "id": "2365",
"uuid": "80eec4c6-70bd-4beb-b3d4-5d46c6343157", "uuid": "80eec4c6-70bd-4beb-b3d4-5d46c6343157",
"type": "community", "type": "community",
"metadata": [ "metadata": [{
{
"key": "dc.description", "key": "dc.description",
"value": "<p>This is the introductory text for the <em>Sample Community</em> on the DSpace Demonstration Site. It is editable by System or Community Administrators (of this Community).</p>\r\n<p><strong>DSpace Communities may contain one or more Sub-Communities or Collections (of Items).</strong></p>\r\n<p>This particular Community has its own logo (the <a href=\"http://www.duraspace.org/\">DuraSpace</a> logo).</p>", "value": "<p>This is the introductory text for the <em>Sample Community</em> on the DSpace Demonstration Site. It is editable by System or Community Administrators (of this Community).</p>\r\n<p><strong>DSpace Communities may contain one or more Sub-Communities or Collections (of Items).</strong></p>\r\n<p>This particular Community has its own logo (the <a href=\"http://www.duraspace.org/\">DuraSpace</a> logo).</p>",
"language": null "language": null
@@ -80,10 +79,10 @@ export const COMMUNITIES = {
"self": { "self": {
"href": "/communities/2365" "href": "/communities/2365"
}, },
"collections": [ "collections": [{
{ "href": "/collections/6547" } "href": "/collections/6547"
] }]
} }
} }
] ]
}; }

View File

@@ -1,26 +1,19 @@
export const ITEMS = {
"items": [
{ {
"items": [{
"_links": { "_links": {
"self": { "self": {
"href": "/items/8871" "href": "/items/8871"
}, },
"parents": [ "parents": [{
{
"href": "/collections/5179" "href": "/collections/5179"
}, },
{ {
"href": "/collections/6547" "href": "/collections/6547"
} }
], ],
"bundles": [ "bundles": [{
{
"href": "/bundles/2355" "href": "/bundles/2355"
}, }]
// {
// "href": "/bundles/5687"
// }
]
}, },
"id": "8871", "id": "8871",
"uuid": "21539b1d-9ef1-4eda-9c77-49565b5bfb78", "uuid": "21539b1d-9ef1-4eda-9c77-49565b5bfb78",
@@ -30,8 +23,7 @@ export const ITEMS = {
"lastModified": "2016-10-14 10:41:12.886", "lastModified": "2016-10-14 10:41:12.886",
"isArchived": true, "isArchived": true,
"isWithdrawn": false, "isWithdrawn": false,
"metadata": [ "metadata": [{
{
"key": "dc.contributor.author", "key": "dc.contributor.author",
"value": "Antelman, Kristin", "value": "Antelman, Kristin",
"language": "en" "language": "en"
@@ -93,13 +85,17 @@ export const ITEMS = {
} }
], ],
"_embedded": { "_embedded": {
"parents": [ "parents": [{
{
"_links": { "_links": {
"self": { "href": "/collections/6547" }, "self": {
"items": [ "href": "/collections/6547"
{ "href": "/items/8871" }, },
{ "href": "/items/9978" } "items": [{
"href": "/items/8871"
},
{
"href": "/items/9978"
}
] ]
}, },
"id": "6547", "id": "6547",
@@ -107,8 +103,7 @@ export const ITEMS = {
"type": "collection", "type": "collection",
"name": "Another Test Collection", "name": "Another Test Collection",
"handle": "123456789/6547", "handle": "123456789/6547",
"metadata": [ "metadata": [{
{
"key": "dc.rights", "key": "dc.rights",
"value": "<p>© 2005-2016 JOHN DOE SOME RIGHTS RESERVED</p>", "value": "<p>© 2005-2016 JOHN DOE SOME RIGHTS RESERVED</p>",
"language": null "language": null
@@ -129,34 +124,43 @@ export const ITEMS = {
"language": null "language": null
} }
] ]
} }],
], "bundles": [{
"bundles": [
{
"_links": { "_links": {
"self": { "href": "/bundles/2355" }, "self": {
"items": [ "href": "/bundles/2355"
{ "href": "/items/8871" } },
], "items": [{
"bitstreams": [ "href": "/items/8871"
{ "href": "/bitstreams/3678" }, }],
], "bitstreams": [{
"primaryBitstream": { "href": "/bitstreams/3678" } "href": "/bitstreams/3678"
}],
"primaryBitstream": {
"href": "/bitstreams/3678"
}
}, },
"id": "2355", "id": "2355",
"uuid": "35e0606d-5e18-4f9c-aa61-74fc751cc3f9", "uuid": "35e0606d-5e18-4f9c-aa61-74fc751cc3f9",
"type": "bundle", "type": "bundle",
"name": "ORIGINAL", "name": "ORIGINAL",
"metadata": [ "metadata": [{
{ "key": "dc.title", "value": "ORIGINAL", "language": "en" } "key": "dc.title",
], "value": "ORIGINAL",
"language": "en"
}],
"_embedded": { "_embedded": {
"bitstreams": [ "bitstreams": [{
{
"_links": { "_links": {
"self": { "href": "/bitstreams/3678" }, "self": {
"bundle": { "href": "/bundles/35e0606d-5e18-4f9c-aa61-74fc751cc3f9" }, "href": "/bitstreams/3678"
"retrieve": { "href": "/bitstreams/43c57c2b-206f-4645-8c8f-5f10c84b09fa/retrieve" } },
"bundle": {
"href": "/bundles/35e0606d-5e18-4f9c-aa61-74fc751cc3f9"
},
"retrieve": {
"href": "/bitstreams/43c57c2b-206f-4645-8c8f-5f10c84b09fa/retrieve"
}
}, },
"id": "3678", "id": "3678",
"uuid": "43c57c2b-206f-4645-8c8f-5f10c84b09fa", "uuid": "43c57c2b-206f-4645-8c8f-5f10c84b09fa",
@@ -167,17 +171,22 @@ export const ITEMS = {
"value": "063dfbbbac873aa3fca479b878eccff3", "value": "063dfbbbac873aa3fca479b878eccff3",
"algorithm": "MD5" "algorithm": "MD5"
}, },
"metadata": [ "metadata": [{
{ "key": "dc.title", "value": "do_open_access_CRL.pdf", "language": null }, "key": "dc.title",
{ "key": "dc.description", "value": "Conference Paper", "language": "en" } "value": "do_open_access_CRL.pdf",
"language": null
},
{
"key": "dc.description",
"value": "Conference Paper",
"language": "en"
}
], ],
"format": "Adobe PDF", "format": "Adobe PDF",
"mimetype": "application/pdf" "mimetype": "application/pdf"
}]
} }
] }]
}
}
]
} }
}, },
{ {
@@ -185,22 +194,16 @@ export const ITEMS = {
"self": { "self": {
"href": "/items/9978" "href": "/items/9978"
}, },
"parents": [ "parents": [{
{
"href": "/collections/5179" "href": "/collections/5179"
}, },
{ {
"href": "/collections/6547" "href": "/collections/6547"
} }
], ],
"bundles": [ "bundles": [{
{
"href": "/bundles/2355" "href": "/bundles/2355"
}, }]
// {
// "href": "/bundles/5687"
// }
]
}, },
"id": "9978", "id": "9978",
"uuid": "be8325f7-243b-49f4-8a4b-df2b793ff3b5", "uuid": "be8325f7-243b-49f4-8a4b-df2b793ff3b5",
@@ -210,8 +213,7 @@ export const ITEMS = {
"lastModified": "2016-05-27 03:00:20.063", "lastModified": "2016-05-27 03:00:20.063",
"isArchived": true, "isArchived": true,
"isWithdrawn": false, "isWithdrawn": false,
"metadata": [ "metadata": [{
{
"key": "dc.contributor.author", "key": "dc.contributor.author",
"value": "John Doe", "value": "John Doe",
"language": "en" "language": "en"
@@ -253,23 +255,28 @@ export const ITEMS = {
} }
], ],
"_embedded": { "_embedded": {
"parents": [ "parents": [{
{
"_links": { "_links": {
"self": { "href": "/collections/5179" }, "self": {
"items": [ "href": "/collections/5179"
{ "href": "/items/8871" }, },
{ "href": "/items/9978" } "items": [{
"href": "/items/8871"
},
{
"href": "/items/9978"
}
], ],
"logo": { "href": "/bitstreams/4688" } "logo": {
"href": "/bitstreams/4688"
}
}, },
"id": "5179", "id": "5179",
"uuid": "9e32a2e2-6b91-4236-a361-995ccdc14c60", "uuid": "9e32a2e2-6b91-4236-a361-995ccdc14c60",
"type": "collection", "type": "collection",
"name": "A Test Collection", "name": "A Test Collection",
"handle": "123456789/5179", "handle": "123456789/5179",
"metadata": [ "metadata": [{
{
"key": "dc.rights", "key": "dc.rights",
"value": "<p>© 2005-2016 JOHN DOE SOME RIGHTS RESERVED</p>", "value": "<p>© 2005-2016 JOHN DOE SOME RIGHTS RESERVED</p>",
"language": null "language": null
@@ -293,10 +300,15 @@ export const ITEMS = {
}, },
{ {
"_links": { "_links": {
"self": { "href": "/collections/6547" }, "self": {
"items": [ "href": "/collections/6547"
{ "href": "/items/8871" }, },
{ "href": "/items/9978" } "items": [{
"href": "/items/8871"
},
{
"href": "/items/9978"
}
] ]
}, },
"id": "6547", "id": "6547",
@@ -304,8 +316,7 @@ export const ITEMS = {
"type": "collection", "type": "collection",
"name": "Another Test Collection", "name": "Another Test Collection",
"handle": "123456789/6547", "handle": "123456789/6547",
"metadata": [ "metadata": [{
{
"key": "dc.rights", "key": "dc.rights",
"value": "<p>© 2005-2016 JOHN DOE SOME RIGHTS RESERVED</p>", "value": "<p>© 2005-2016 JOHN DOE SOME RIGHTS RESERVED</p>",
"language": null "language": null
@@ -331,4 +342,4 @@ export const ITEMS = {
} }
} }
] ]
}; }

View File

@@ -1,6 +1,5 @@
export const METADATA = {
"metadata": [
{ {
"metadata": [{
"type": "metadata", "type": "metadata",
"id": "d58a3098-b390-4cd6-8f52-b088b3daa637", "id": "d58a3098-b390-4cd6-8f52-b088b3daa637",
"attributes": { "attributes": {
@@ -181,4 +180,4 @@ export const METADATA = {
} }
} }
] ]
}; }

View File

@@ -1,7 +1,7 @@
// Our API for demos only // Our API for demos only
export const fakeDataBase = { export const fakeDataBase = {
get() { get() {
let res = { data: 'This fake data came from the db on the server.' }; const res = { data: 'This fake data came from the db on the server.' };
return Promise.resolve(res); return Promise.resolve(res);
} }
}; };

View File

@@ -1,36 +0,0 @@
// the polyfills must be the first thing imported
import 'angular2-universal-polyfills';
import 'ts-helpers';
import './platform/workarounds/__workaround.browser'; // temporary until 2.1.1 things are patched in Core
// Angular 2
import { enableProdMode } from '@angular/core';
import { platformBrowser } from '@angular/platform-browser';
import { bootloader } from '@angularclass/bootloader';
// for AoT use platformBrowser
// import { platformUniversalDynamic } from 'angular2-universal/browser';
import { load as loadWebFont } from 'webfontloader';
// enable prod for faster renders
enableProdMode();
import { MainModuleNgFactory } from './platform/modules/browser.module.ngfactory';
export const platformRef = platformBrowser();
// on document ready bootstrap Angular 2
export function main() {
// Load fonts async
// https://github.com/typekit/webfontloader#configuration
loadWebFont({
google: {
families: ['Droid Sans']
}
});
return platformRef.bootstrapModuleFactory(MainModuleNgFactory);
}
// support async tag or hmr
bootloader(main);

View File

@@ -1,38 +0,0 @@
// the polyfills must be the first thing imported
import 'angular2-universal-polyfills';
import 'ts-helpers';
import './platform/workarounds/__workaround.browser'; // temporary until 2.1.1 things are patched in Core
// Angular 2
import { enableProdMode } from '@angular/core';
import { platformUniversalDynamic } from 'angular2-universal/browser';
import { bootloader } from '@angularclass/bootloader';
import { load as loadWebFont } from 'webfontloader';
import { EnvConfig } from './config';
if (EnvConfig.production) {
// enable prod for faster renders
enableProdMode();
}
import { MainModule } from './platform/modules/browser.module';
export const platformRef = platformUniversalDynamic();
// on document ready bootstrap Angular 2
export function main() {
// Load fonts async
// https://github.com/typekit/webfontloader#configuration
loadWebFont({
google: {
families: ['Droid Sans']
}
});
return platformRef.bootstrapModule(MainModule);
}
// support async tag or hmr
bootloader(main);

View File

@@ -1,78 +1,55 @@
// Look in ./config folder for config import { InjectionToken } from '@angular/core';
import { OpaqueToken } from '@angular/core';
interface ServerConfig { import { ServerConfig } from './config/server-config.interface';
"ssl": boolean; import { GlobalConfig } from './config/global-config.interface';
"address": string;
"port": number; const GLOBAL_CONFIG: InjectionToken<GlobalConfig> = new InjectionToken<GlobalConfig>('config');
"nameSpace": string;
"baseUrl": string; let production = false;
let ENV_CONFIG: GlobalConfig;
try {
ENV_CONFIG = require('../config/environment.default.js') as GlobalConfig;
} catch (e) {
throw new Error('Cannot find file config/environment.default.js');
} }
interface GlobalConfig {
"production": boolean;
"rest": ServerConfig;
"ui": ServerConfig;
"cache": {
"msToLive": number,
"control": string
};
"universal": {
"preboot": boolean,
"async": boolean
};
}
const GLOBAL_CONFIG = new OpaqueToken('config');
let configContext = require.context("../config", false, /js$/);
let EnvConfig: GlobalConfig;
let EnvConfigFile: string;
let production: boolean = false;
// check process.env.NODE_ENV to determine which environment config to use
// process.env.NODE_ENV is defined by webpack, else assume development
switch (process.env.NODE_ENV) { switch (process.env.NODE_ENV) {
case 'prod': case 'prod':
case 'production': case 'production':
// webpack.prod.config.ts defines process.env.NODE_ENV = 'production'
EnvConfigFile = './environment.prod.js';
production = true; production = true;
try {
ENV_CONFIG = Object.assign(ENV_CONFIG, require('../config/environment.prod.js')) as GlobalConfig;
} catch (e) {
console.warn('Cannot find file config/environment.prod.js', 'Using default environment.');
}
break; break;
case 'test': case 'test':
// webpack.test.config.ts defines process.env.NODE_ENV = 'test' try {
EnvConfigFile = './environment.test.js'; ENV_CONFIG = Object.assign(ENV_CONFIG, require('../config/environment.test.js')) as GlobalConfig;
} catch (e) {
console.warn('Cannot find file config/environment.test.js', 'Using default environment.');
}
break; break;
default: default:
// if not using webpack.prod.config.ts or webpack.test.config.ts, it must be development
EnvConfigFile = './environment.dev.js';
}
try { try {
EnvConfig = configContext('./environment.default.js'); ENV_CONFIG = Object.assign(ENV_CONFIG, require('../config/environment.dev.js')) as GlobalConfig;
} catch (e) { } catch (e) {
throw new Error("Cannot find file environment.default.js"); console.warn('Cannot find file config/environment.dev.js', 'Using default environment.');
}
// if EnvConfigFile set try to get configs
if (EnvConfigFile) {
try {
EnvConfig = <GlobalConfig>Object.assign(EnvConfig, configContext(EnvConfigFile));
} catch (e) {
console.warn("Cannot find file " + EnvConfigFile.substring(2, EnvConfigFile.length), "Using default environment.");
} }
} }
// set base url if property is object with ssl, address, and port. i.e. ServerConfig ENV_CONFIG.production = production;
for (let key in EnvConfig) {
if (EnvConfig[key].ssl !== undefined && EnvConfig[key].address && EnvConfig[key].port) { for (const key in ENV_CONFIG) {
EnvConfig[key].baseUrl = [EnvConfig[key].ssl ? 'https://' : 'http://', EnvConfig[key].address, (EnvConfig[key].port !== 80) ? ':' + EnvConfig[key].port : ''].join(''); if (ENV_CONFIG[key].host) {
ENV_CONFIG[key].baseUrl = [
ENV_CONFIG[key].ssl ? 'https://' : 'http://',
ENV_CONFIG[key].host,
ENV_CONFIG[key].port ? (ENV_CONFIG[key].port !== 80 || ENV_CONFIG[key].port !== 443) ? ':' + ENV_CONFIG[key].port : '' : ''
].join('');
} }
} }
// set config for whether running in production export { GlobalConfig, GLOBAL_CONFIG, ENV_CONFIG }
EnvConfig.production = production;
export { GLOBAL_CONFIG, GlobalConfig, EnvConfig }

Some files were not shown because too many files have changed in this diff Show More