diff --git a/.gitignore b/.gitignore index 30a42d7f81..0e713600f8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ webpack.records.json /npm-debug.log.* morgan.log + +*.css diff --git a/package.json b/package.json index cc35b2a022..abad50f2a6 100644 --- a/package.json +++ b/package.json @@ -7,102 +7,118 @@ "url": "https://github.com/dspace/dspace-angular.git" }, "scripts": { - "watch": "webpack --watch", - "watch:dev": "npm run server & npm run watch", - "clean:dist": "rimraf dist", - "clean:ngc": "rimraf **/*.ngfactory.ts **/*.css.shim.ts **/*.scss.shim.ts", - "prebuild": "npm run clean:dist", - "build": "webpack --progress", - "build:prod:ngc": "npm run clean:ngc && npm run ngc && npm run clean:dist && npm run build:prod", - "build:prod:ngc:json": "npm run clean:ngc && npm run ngc && npm run clean:dist && npm run build:prod:json", + "clean:log": "rimraf *.log*", + "clean:dist": "rimraf dist/*", + "clean:node": "rimraf node_modules/*", + "clean:ngc": "rimraf **/*.ngfactory.ts", + "clean:json": "rimraf *.records.json", + "clean:css": "rimraf **/*.css", + "clean:css:ts": "rimraf **/*.css.ts", + "clean:scss:ts": "rimraf **/*.scss.ts", + "clean:css:shim:ts": "rimraf **/*.css.shim.ts", + "clean:scss:shim:ts": "rimraf **/*.scss.shim.ts", + "clean:prod": "npm run clean:ngc && npm run clean:json && npm run clean:css && npm run clean:css:ts && npm run clean:scss:ts && npm run clean:css:shim:ts && npm run clean:scss:shim:ts && npm run clean:dist", + "clean": "npm run clean:log && npm run clean:dist && npm run clean:prod && npm run clean:node", + "sass": "node-sass src -o src --include-path node_modules --output-style compressed -q", + "sass:watch": "node-sass -w src -o src --include-path node_modules --output-style compressed -q", + "prebuild": "npm run clean:dist && npm run sass", + "build": "webpack --progress", "build:prod": "webpack --config webpack.prod.config.ts", + "build:prod:ngc": "npm run clean:prod && npm run sass && npm run ngc && npm run clean:dist && npm run build:prod", + "build:prod:ngc:json": "npm run clean:prod && npm run sass && npm run ngc && npm run clean:dist && npm run build:prod:json", "build:prod:json": "webpack --config webpack.prod.config.ts --json | webpack-bundle-size-analyzer", "ngc": "ngc -p tsconfig.aot.json", - "prestart": "npm run build", - "server": "nodemon dist/server/index.js", - "debug:server": "node-nightly --inspect --debug-brk dist/server/index.js", + "prestart": "npm run build:prod:ngc:json", + "server": "node dist/server/index.js", + "server:dev": "nodemon --debug dist/server/index.js", "start": "npm run server", - "start:prod:node": "node dist/server/index.js", - "start:prod": "npm run build:prod:ngc:json && npm run start:prod:node", - "debug:start": "npm run build && npm run debug:server", + "start:dev": "npm run clean:prod && npm run build && npm run server", + "watch": "webpack -w & npm run sass:watch", + "watch:dev:server": "concurrently \"npm run server:dev\" \"npm run watch\"", + "watch:dev": "npm run clean:prod && npm run build && npm run watch:dev:server", + "watch:prod:server": "concurrently \"npm run server\" \"npm run watch\"", + "watch:prod": "npm run build:prod:ngc:json && npm run watch:prod:server", "predebug": "npm run build", + "debug": "node --debug-brk dist/server/index.js", + "debug:server": "node-nightly --inspect --debug-brk dist/server/index.js", + "debug:start": "npm run build && npm run debug:server", "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": "node --debug-brk dist/server/index.js", "lint": "tslint \"src/**/*.ts\" || true" }, "dependencies": { - "@angular/common": "~2.2.4", - "@angular/compiler": "~2.2.4", - "@angular/compiler-cli": "~2.2.4", - "@angular/core": "~2.2.4", - "@angular/forms": "~2.2.4", - "@angular/http": "~2.2.4", - "@angular/platform-browser": "~2.2.4", - "@angular/platform-browser-dynamic": "~2.2.4", - "@angular/platform-server": "~2.2.4", - "@angular/router": "~3.2.4", - "@angular/upgrade": "~2.2.4", - "@angularclass/bootloader": "~1.0.1", - "@angularclass/idle-preload": "~1.0.4", - "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", - "compression": "^1.6.2", - "express": "^4.14.0", + "@angular/common": "2.2.4", + "@angular/compiler": "2.2.4", + "@angular/compiler-cli": "2.2.4", + "@angular/core": "2.2.4", + "@angular/forms": "2.2.4", + "@angular/http": "2.2.4", + "@angular/platform-browser": "2.2.4", + "@angular/platform-browser-dynamic": "2.2.4", + "@angular/platform-server": "2.2.4", + "@angular/router": "3.2.4", + "@angular/upgrade": "2.2.4", + "@angularclass/bootloader": "1.0.1", + "@angularclass/idle-preload": "1.0.4", + "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.14", + "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.5", + "compression": "1.6.2", + "express": "4.14.0", + "font-awesome": "4.7.0", "js.clone": "0.0.3", - "methods": "~1.1.2", - "morgan": "^1.7.0", - "ng2-translate": "~4.0.1", - "preboot": "~4.5.2", + "methods": "1.1.2", + "morgan": "1.7.0", + "ng2-translate": "4.2.0", + "preboot": "4.5.2", "rxjs": "5.0.0-beta.12", - "webfontloader": "^1.6.27", - "zone.js": "~0.6.26" + "webfontloader": "1.6.27", + "zone.js": "0.6.26" }, "devDependencies": { "@ngtools/webpack": "1.1.7", "@types/body-parser": "0.0.33", "@types/compression": "0.0.33", - "@types/cookie-parser": "^1.3.30", - "@types/express": "^4.0.34", - "@types/express-serve-static-core": "^4.0.39", - "@types/hammerjs": "^2.0.33", + "@types/cookie-parser": "1.3.30", + "@types/express": "4.0.34", + "@types/express-serve-static-core": "4.0.39", + "@types/hammerjs": "2.0.33", "@types/memory-cache": "0.0.29", "@types/mime": "0.0.29", - "@types/morgan": "^1.7.32", - "@types/node": "^6.0.51", - "@types/serve-static": "^1.7.31", - "@types/webfontloader": "^1.6.27", - "accepts": "^1.3.3", - "angular2-template-loader": "^0.6.0", - "awesome-typescript-loader": "^2.2.4", + "@types/morgan": "1.7.32", + "@types/node": "6.0.51", + "@types/serve-static": "1.7.31", + "@types/webfontloader": "1.6.27", + "angular2-template-loader": "0.6.0", + "autoprefixer": "6.5.3", + "awesome-typescript-loader": "2.2.4", "codelyzer": "2.0.0-beta.1", - "cookie-parser": "^1.4.3", - "copy-webpack-plugin": "~4.0.1", - "express-interceptor": "^1.2.0", - "iltorb": "^1.0.13", - "imports-loader": "^0.6.5", - "json-loader": "^0.5.4", - "memory-cache": "^0.1.6", - "node-sass": "^3.13.0", - "nodemon": "^1.11.0", - "raw-loader": "^0.5.1", + "concurrently": "3.1.0", + "cookie-parser": "1.4.3", + "copy-webpack-plugin": "4.0.1", + "imports-loader": "0.6.5", + "json-loader": "0.5.4", + "node-sass": "3.13.0", + "nodemon": "1.11.0", + "raw-loader": "0.5.1", "reflect-metadata": "0.1.8", - "rimraf": "^2.5.4", - "sass-loader": "^4.0.2", - "string-replace-loader": "^1.0.5", - "ts-helpers": "^1.1.2", - "ts-node": "^1.7.0", + "rimraf": "2.5.4", + "string-replace-loader": "1.0.5", + "ts-helpers": "1.1.2", + "ts-node": "1.7.0", "tslint": "4.0.2", - "tslint-loader": "3.2.1", + "tslint-loader": "3.3.0", + "typedoc": "0.5.1", "typescript": "2.0.10", - "v8-lazy-parse-webpack-plugin": "^0.3.0", + "v8-lazy-parse-webpack-plugin": "0.3.0", "webpack": "2.1.0-beta.27", "webpack-bundle-analyzer": "1.4.1", - "webpack-dev-middleware": "^1.8.4", + "webpack-dev-middleware": "1.8.4", "webpack-dev-server": "2.1.0-beta.11", - "webpack-merge": "~1.0.2" + "webpack-merge": "1.0.2" } } diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 26237a935d..60b85b4f65 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -1,5 +1,5 @@ { - "title": "DSpace Universal", + "title": "DSpace", "nav": { "home": "Home" diff --git a/resources/images/dspace_logo.png b/resources/images/dspace_logo.png new file mode 100644 index 0000000000..c0ac215f4a Binary files /dev/null and b/resources/images/dspace_logo.png differ diff --git a/src/app/app.component.html b/src/app/app.component.html index a83351f943..83a269739f 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,7 +1,22 @@ -

{{ 'title' | translate }}

- +
+ +
+

{{ 'example.with.data' | translate:data }}

diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 8b13789179..dc8b94f882 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -1 +1,13 @@ +@import '../styles/variables.scss'; +header nav.navbar { + border-radius: 0rem; +} + +header nav.navbar .navbar-toggler { + float: right; +} + +header nav.navbar .navbar-toggler:hover { + cursor: pointer; +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 39f73ee36b..5f1726afd0 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,5 @@ -import { Component, ChangeDetectionStrategy, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { Component, HostListener, Input, ChangeDetectionStrategy, ViewEncapsulation, OnDestroy, OnInit } from '@angular/core'; +import { Event, NavigationEnd, Router } from '@angular/router'; import { TranslateService } from 'ng2-translate'; @@ -7,10 +8,19 @@ import { TranslateService } from 'ng2-translate'; encapsulation: ViewEncapsulation.Emulated, selector: 'ds-app', templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + styleUrls: ['./app.component.css'] }) export class AppComponent implements OnDestroy, OnInit { + // TODO: move header and all related properties into its own component + + private navCollapsed: boolean; + + private routerSubscription: any; + + + private translateSubscription: any; + example: string; data: any = { @@ -18,25 +28,54 @@ export class AppComponent implements OnDestroy, OnInit { recipient: 'World' } - private registerSubscription: any; - - constructor(public translate: TranslateService) { + constructor(public translate: TranslateService, private router: Router) { // this language will be used as a fallback when a translation isn't found in the current language translate.setDefaultLang('en'); // the lang to use, if the lang isn't available, it will use the current loader to get them translate.use('en'); + + this.collapse(); } ngOnInit() { - this.registerSubscription = this.translate.get('example.with.data', { greeting: 'Hello', recipient: 'DSpace' }).subscribe((translation: string) => { + this.routerSubscription = this.router.events.subscribe((event: Event) => { + if (event instanceof NavigationEnd) { + this.collapse(); + } + }); + this.translateSubscription = this.translate.get('example.with.data', { greeting: 'Hello', recipient: 'DSpace' }).subscribe((translation: string) => { this.example = translation; }); } ngOnDestroy() { - if (this.registerSubscription) { - this.registerSubscription.unsubscribe(); + if (this.routerSubscription) { + this.routerSubscription.unsubscribe(); + } + if (this.translateSubscription) { + this.translateSubscription.unsubscribe(); } } + @HostListener('window:resize', ['$event']) + private onResize(event): void { + this.collapse(); + } + + private collapse(): void { + this.navCollapsed = true; + } + + private expand(): void { + this.navCollapsed = false; + } + + public toggle(): void { + this.navCollapsed ? this.expand() : this.collapse(); + } + + public isNavBarCollaped(): boolean { + return this.navCollapsed; + } + } diff --git a/src/app/home/home.component.css b/src/app/home/home.component.css deleted file mode 100644 index 17b877f70b..0000000000 --- a/src/app/home/home.component.css +++ /dev/null @@ -1,8 +0,0 @@ -blockquote { - border-left:5px #158126 solid; - background:#fff; - padding:20px 20px 20px 40px; -} -blockquote::before { - left: 1em; -} diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss new file mode 100644 index 0000000000..da97dd7a62 --- /dev/null +++ b/src/app/home/home.component.scss @@ -0,0 +1 @@ +@import '../../styles/variables.scss'; diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 71d5be23ff..9cb0704a0a 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -8,12 +8,15 @@ import { Component, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/ templateUrl: './home.component.html' }) export class HomeComponent { + data: any = {}; + constructor() { this.universalInit(); } universalInit() { + } } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 0f8b5e5625..91b9d1b87d 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from 'ng2-translate/ng2-translate'; import { ApiService } from './api.service'; @@ -14,7 +15,8 @@ const MODULES = [ RouterModule, TranslateModule, FormsModule, - ReactiveFormsModule + ReactiveFormsModule, + NgbModule ]; const PIPES = [ diff --git a/src/browser.module.ts b/src/browser.module.ts index e6475cc118..20c87fd924 100755 --- a/src/browser.module.ts +++ b/src/browser.module.ts @@ -5,6 +5,7 @@ import { RouterModule } from '@angular/router'; import { UniversalModule, isBrowser, isNode, AUTO_PREBOOT } from 'angular2-universal/browser'; // for AoT we need to manually split universal packages import { IdlePreload, IdlePreloadModule } from '@angularclass/idle-preload'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateLoader, TranslateModule, TranslateStaticLoader } from 'ng2-translate'; import { AppModule, AppComponent } from './app/app.module'; @@ -47,6 +48,7 @@ export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE'; useFactory: (createTranslateLoader), deps: [Http] }), + NgbModule.forRoot(), UniversalModule, // BrowserModule, HttpModule, and JsonpModule are included diff --git a/src/index.html b/src/index.html index 979a468fcb..22c60b4ae9 100644 --- a/src/index.html +++ b/src/index.html @@ -2,22 +2,18 @@ - DSpace + DSpace - - + - - Loading DSpace ... - diff --git a/src/node.module.ts b/src/node.module.ts index 5395e54750..1a7ee825c8 100755 --- a/src/node.module.ts +++ b/src/node.module.ts @@ -4,6 +4,7 @@ import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { UniversalModule, isBrowser, isNode } from 'angular2-universal/node'; // for AoT we need to manually split universal packages +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateLoader, TranslateModule, TranslateStaticLoader } from 'ng2-translate'; import { AppModule, AppComponent } from './app/app.module'; @@ -39,6 +40,7 @@ export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE'; useFactory: (createTranslateLoader), deps: [Http] }), + NgbModule.forRoot(), UniversalModule, // BrowserModule, HttpModule, and JsonpModule are included diff --git a/src/server.aot.ts b/src/server.aot.ts index c7e62a929c..7df96cb3b9 100644 --- a/src/server.aot.ts +++ b/src/server.aot.ts @@ -8,16 +8,11 @@ import './__workaround.node'; // temporary until 2.1.1 things are patched in Cor import * as fs from 'fs'; import * as path from 'path'; +import * as morgan from 'morgan'; import * as express from 'express'; import * as bodyParser from 'body-parser'; +import * as compression from 'compression'; import * as cookieParser from 'cookie-parser'; -import * as morgan from 'morgan'; -import * as mcache from 'memory-cache'; - -const { gzipSync } = require('zlib'); -const accepts = require('accepts'); -const { compressSync } = require('iltorb'); -const interceptor = require('express-interceptor'); // Angular 2 import { enableProdMode } from '@angular/core'; @@ -54,38 +49,7 @@ app.set('json spaces', 2); app.use(cookieParser('Angular 2 Universal')); app.use(bodyParser.json()); - -app.use(interceptor((req, res) => ({ - // don't compress responses with this request header - isInterceptable: () => (!req.headers['x-no-compression']), - intercept: (body, send) => { - const encodings = new Set(accepts(req).encodings()); - const bodyBuffer = new Buffer(body); - // url specific key for response cache - const key = '__response__' + req.originalUrl || req.url; - let output = bodyBuffer; - // check if cache exists - if (mcache.get(key) === null) { - // check for encoding support - if (encodings.has('br')) { - // brotli - res.setHeader('Content-Encoding', 'br'); - output = compressSync(bodyBuffer); - mcache.put(key, { output, encoding: 'br' }); - } else if (encodings.has('gzip')) { - // gzip - res.setHeader('Content-Encoding', 'gzip'); - output = gzipSync(bodyBuffer); - mcache.put(key, { output, encoding: 'gzip' }); - } - } else { - const { output, encoding } = mcache.get(key); - res.setHeader('Content-Encoding', encoding); - send(output); - } - send(output); - } -}))); +app.use(compression()); const accessLogStream = fs.createWriteStream(ROOT + '/morgan.log', { flags: 'a' }) @@ -101,6 +65,8 @@ function cacheControl(req, res, next) { } // Serve static files app.use('/assets', cacheControl, express.static(path.join(__dirname, 'assets'), { maxAge: 30 })); +app.use('/styles', cacheControl, express.static(path.join(__dirname, 'styles'), { maxAge: 30 })); + app.use(cacheControl, express.static(path.join(ROOT, 'dist/client'), { index: false })); // @@ -117,7 +83,8 @@ function ngApp(req, res) { req, res, // time: true, // use this to determine what part of your app is slow only in development - preboot: false, + async: true, + preboot: true, baseUrl: '/', requestUrl: req.originalUrl, originUrl: `http://localhost:${app.get('port')}` diff --git a/src/server.ts b/src/server.ts index f6393a391c..5c4250c2f5 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,11 +7,11 @@ import 'ts-helpers'; import './__workaround.node'; // temporary until 2.1.1 things are patched in Core import * as path from 'path'; +import * as morgan from 'morgan'; import * as express from 'express'; import * as bodyParser from 'body-parser'; -import * as cookieParser from 'cookie-parser'; -import * as morgan from 'morgan'; import * as compression from 'compression'; +import * as cookieParser from 'cookie-parser'; // Angular 2 import { enableProdMode } from '@angular/core'; @@ -58,6 +58,8 @@ function cacheControl(req, res, next) { } // Serve static files app.use('/assets', cacheControl, express.static(path.join(__dirname, 'assets'), { maxAge: 30 })); +app.use('/styles', cacheControl, express.static(path.join(__dirname, 'styles'), { maxAge: 30 })); + app.use(cacheControl, express.static(path.join(ROOT, 'dist/client'), { index: false })); // @@ -74,7 +76,8 @@ function ngApp(req, res) { req, res, // time: true, // use this to determine what part of your app is slow only in development - preboot: false, + async: true, + preboot: true, baseUrl: '/', requestUrl: req.originalUrl, originUrl: `http://localhost:${app.get('port')}` diff --git a/src/styles/main.scss b/src/styles/main.scss new file mode 100644 index 0000000000..a150af617b --- /dev/null +++ b/src/styles/main.scss @@ -0,0 +1,10 @@ +@import './variables.scss'; + +@import '../../node_modules/bootstrap/scss/bootstrap.scss'; + +@import "../../node_modules/font-awesome/scss/font-awesome.scss"; + +html { + position: relative; + min-height: 100%; +} diff --git a/src/styles/variables.scss b/src/styles/variables.scss new file mode 100644 index 0000000000..64bf45be18 --- /dev/null +++ b/src/styles/variables.scss @@ -0,0 +1,24 @@ +// Colors + +$gray-base: #000 !default; +$gray-darker: lighten($gray-base, 13.5%) !default; // #222 +$gray-dark: lighten($gray-base, 26.6%) !default; // #444 +$gray: lighten($gray-base, 46.6%) !default; // #777 +$gray-light: lighten($gray-base, 73.3%) !default; // #bbb +$gray-lighter: lighten($gray-base, 93.5%) !default; // #eee + +$brand-primary: #2B4E72 !default; +$brand-success: #94BA65 !default; +$brand-info: #2790B0 !default; +$brand-warning: #EBBB54 !default; +$brand-danger: #CF4444 !default; + +$brand-inverse: $brand-primary !default; + +$link-color: $brand-info !default; + +// Fonts + +$fa-font-path: "../assets/fonts"; + +$image-path: "../assets/images"; diff --git a/webpack.config.ts b/webpack.config.ts index 63fae60aa4..1d4b927e13 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -14,10 +14,20 @@ export var commonPlugins = [ } ), - new CopyWebpackPlugin([{ - from: path.join(__dirname, 'resources', 'i18n'), - to: path.join('assets', 'i18n') - }]), + // Copy fonts, images and i18n to dist/assets + new CopyWebpackPlugin([ + { + from: path.join(__dirname, 'node_modules', 'font-awesome', 'fonts'), + to: path.join('assets', 'fonts') + }, + { + from: path.join(__dirname, 'resources', 'images'), + to: path.join('assets', 'images') + }, { + from: path.join(__dirname, 'resources', 'i18n'), + to: path.join('assets', 'i18n') + } + ]), // Loader options new webpack.LoaderOptionsPlugin({ @@ -48,7 +58,6 @@ export var commonConfig = { { test: /\.ts$/, use: ['awesome-typescript-loader', 'angular2-template-loader'] }, { test: /\.html$/, use: 'raw-loader' }, { test: /\.css$/, use: 'raw-loader' }, - { test: /\.scss$/, use: ['raw-loader', 'sass-loader'] }, { test: /\.json$/, use: 'json-loader' }, { enforce: 'pre',