1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-09-16 09:16:27 +02:00

adding angular-cli support (causes major refactoring)

This commit is contained in:
Braun Patrik
2017-06-10 22:32:56 +02:00
parent e7cb6311a9
commit 8b9f287a88
108 changed files with 3820 additions and 3752 deletions

58
.angular-cli.json Normal file
View File

@@ -0,0 +1,58 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "pigallery2"
},
"apps": [
{
"root": "frontend",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico",
"config_inject.ejs"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json"
},
{
"project": "src/tsconfig.spec.json"
},
{
"project": "test/e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"component": {}
}
}

13
.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

9
.gitignore vendored
View File

@@ -2,10 +2,8 @@
PiGallery2.iml
node_modules/
pigallery2.zip
frontend/app/**/*.js
frontend/app/**/*.js.map
frontend/main.js
frontend/main.js.map
frontend/**/*.js
frontend/**/*.js.map
frontend/dist
backend/**/*.js
backend/**/*.js.map
@@ -14,6 +12,9 @@ common/**/*.js.map
test/coverage
test/backend/**/*.js
test/backend/**/*.js.map
test/e2e/**/*.js
test/e2e/**/*.js.map
demo/TEMP/
config.json
users.db
dist/

View File

@@ -21,4 +21,4 @@ class ProjectPathClass {
}
}
export let ProjectPath = new ProjectPathClass();
export const ProjectPath = new ProjectPathClass();

View File

@@ -5,7 +5,7 @@ import {Message} from "../../common/entities/Message";
export class RenderingMWs {
public static renderResult(req:Request, res:Response, next:NextFunction) {
public static renderResult(req: Request, res: Response, next: NextFunction) {
if (!req.resultPipe)
return next();
@@ -13,7 +13,7 @@ export class RenderingMWs {
}
public static renderSessionUser(req:Request, res:Response, next:NextFunction) {
public static renderSessionUser(req: Request, res: Response, next: NextFunction) {
if (!(req.session.user)) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
@@ -23,19 +23,19 @@ export class RenderingMWs {
RenderingMWs.renderMessage(res, user);
}
public static renderFile(req:Request, res:Response, next:NextFunction) {
public static renderFile(req: Request, res: Response, next: NextFunction) {
if (!req.resultPipe)
return next();
return res.sendFile(req.resultPipe);
}
public static renderOK(req:Request, res:Response, next:NextFunction) {
public static renderOK(req: Request, res: Response, next: NextFunction) {
let message = new Message<string>(null, "ok");
res.json(message);
}
public static renderError(err:any, req:Request, res:Response, next:NextFunction):any {
public static renderError(err: any, req: Request, res: Response, next: NextFunction): any {
if (err instanceof Error) {
let message = new Message<any>(err, null);
return res.json(message);
@@ -44,7 +44,7 @@ export class RenderingMWs {
}
protected static renderMessage<T>(res:Response, content:T) {
protected static renderMessage<T>(res: Response, content: T) {
let message = new Message<T>(null, content);
res.json(message);
}

View File

@@ -9,7 +9,7 @@ import {ContentWrapper} from "../../../common/entities/ConentWrapper";
import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
import {ProjectPath} from "../../ProjectPath";
import {PhotoDTO} from "../../../common/entities/PhotoDTO";
import {hardwareRenderer, softwareRenderer} from "./THRenderers";
import {hardwareRenderer, RendererInput, softwareRenderer} from "./THRenderers";
import {Config} from "../../../common/config/private/Config";
@@ -139,20 +139,35 @@ export class ThumbnailGeneratorMWs {
this.initPools();
//run on other thread
pool.send({
let input = <RendererInput>{
imagePath: imagePath,
size: size,
thPath: thPath,
makeSquare: makeSquare,
qualityPriority: Config.Server.thumbnail.qualityPriority,
__dirname: __dirname,
})
};
if (Config.Server.enableThreading == true) {
pool.send(imagePath)
.on('done', (out) => {
return next(out);
}).on('error', (error) => {
console.log(error);
return next(new Error(ErrorCodes.THUMBNAIL_GENERATION_ERROR, error));
});
} else {
try {
if (Config.Server.thumbnail.hardwareAcceleration == true) {
hardwareRenderer(input, out => next(out));
} else {
softwareRenderer(input, out => next(out));
}
}catch (error){
console.log(error);
return next(new Error(ErrorCodes.THUMBNAIL_GENERATION_ERROR, error));
}
}
}
private static generateThumbnailName(imagePath: string, size: number): string {

View File

@@ -1,197 +1,18 @@
///<reference path="exif.d.ts"/>
import * as path from "path";
import {DirectoryDTO} from "../../common/entities/DirectoryDTO";
import {
CameraMetadata,
GPSMetadata,
ImageSize,
PhotoDTO,
PhotoMetadata,
PositionMetaData
} from "../../common/entities/PhotoDTO";
import {ProjectPath} from "../ProjectPath";
import {Logger} from "../Logger";
import {diskManagerTask, DiskManagerTask} from "./DiskMangerTask";
import {Config} from "../../common/config/private/Config";
const Pool = require('threads').Pool;
const pool = new Pool();
const LOG_TAG = "[DiskManager]";
interface PoolInput {
relativeDirectoryName: string;
directoryName: string;
directoryParent: string;
absoluteDirectoryName: string;
}
pool.run(
(input: PoolInput, done) => {
const fs = require("fs");
const path = require("path");
const mime = require("mime");
const iptc = require("node-iptc");
const exif_parser = require("exif-parser");
let isImage = (fullPath: string) => {
let imageMimeTypes = [
'image/bmp',
'image/gif',
'image/jpeg',
'image/png',
'image/pjpeg',
'image/tiff',
'image/webp',
'image/x-tiff',
'image/x-windows-bmp'
];
let extension = mime.lookup(fullPath);
return imageMimeTypes.indexOf(extension) !== -1;
};
let loadPhotoMetadata = (fullPath: string): Promise<PhotoMetadata> => {
return new Promise<PhotoMetadata>((resolve: (metadata: PhotoMetadata) => void, reject) => {
fs.readFile(fullPath, function (err, data) {
if (err) {
return reject({file: fullPath, error: err});
}
try {
const exif = exif_parser.create(data).parse();
const iptcData = iptc(data);
const imageSize: ImageSize = {width: exif.imageSize.width, height: exif.imageSize.height};
const cameraData: CameraMetadata = {
ISO: exif.tags.ISO,
model: exif.tags.Modeol,
maker: exif.tags.Make,
fStop: exif.tags.FNumber,
exposure: exif.tags.ExposureTime,
focalLength: exif.tags.FocalLength,
lens: exif.tags.LensModel,
};
const GPS: GPSMetadata = {
latitude: exif.tags.GPSLatitude,
longitude: exif.tags.GPSLongitude,
altitude: exif.tags.GPSAltitude
};
const positionData: PositionMetaData = {
GPSData: GPS,
country: iptcData.country_or_primary_location_name,
state: iptcData.province_or_state,
city: iptcData.city
};
//Decode characters to UTF8
const decode = (s: any) => {
for (let a, b, i = -1, l = (s = s.split("")).length, o = String.fromCharCode, c = "charCodeAt"; ++i < l;
((a = s[i][c](0)) & 0x80) &&
(s[i] = (a & 0xfc) == 0xc0 && ((b = s[i + 1][c](0)) & 0xc0) == 0x80 ?
o(((a & 0x03) << 6) + (b & 0x3f)) : o(128), s[++i] = "")
);
return s.join("");
};
const keywords: string[] = (iptcData.keywords || []).map((s: string) => decode(s));
const creationDate: number = iptcData.date_time ? iptcData.date_time.getTime() : 0;
const metadata: PhotoMetadata = <PhotoMetadata>{
keywords: keywords,
cameraData: cameraData,
positionData: positionData,
size: imageSize,
creationDate: creationDate
};
return resolve(metadata);
} catch (err) {
return reject({file: fullPath, error: err});
}
});
});
};
let parseDir = (directoryInfo: {
relativeDirectoryName: string,
directoryName: string,
directoryParent: string,
absoluteDirectoryName: string
}, maxPhotos: number = null, photosOnly: boolean = false): Promise<DirectoryDTO> => {
return new Promise<DirectoryDTO>((resolve, reject) => {
let promises: Array<Promise<any>> = [];
let directory = <DirectoryDTO>{
name: directoryInfo.directoryName,
path: directoryInfo.directoryParent,
lastUpdate: Date.now(),
directories: [],
photos: []
};
fs.readdir(directoryInfo.absoluteDirectoryName, (err, list) => {
if (err) {
return reject(err);
}
try {
for (let i = 0; i < list.length; i++) {
let file = list[i];
let fullFilePath = path.normalize(path.resolve(directoryInfo.absoluteDirectoryName, file));
if (photosOnly == false && fs.statSync(fullFilePath).isDirectory()) {
let promise = parseDir({
relativeDirectoryName: path.join(directoryInfo.relativeDirectoryName, path.sep),
directoryName: file,
directoryParent: path.join(directoryInfo.relativeDirectoryName, path.sep),
absoluteDirectoryName: fullFilePath
},
5, true
).then((dir) => {
directory.directories.push(dir);
});
promises.push(promise);
} else if (isImage(fullFilePath)) {
let promise = loadPhotoMetadata(fullFilePath).then((photoMetadata) => {
directory.photos.push(<PhotoDTO>{
name: file,
directory: null,
metadata: photoMetadata
});
});
promises.push(promise);
if (maxPhotos != null && promises.length > maxPhotos) {
break;
}
}
}
Promise.all(promises).then(() => {
return resolve(directory);
}).catch((err) => {
return reject({directoryInfo: directoryInfo, error: err});
});
} catch (err) {
return reject({directoryInfo: directoryInfo, error: err});
}
});
});
};
parseDir(input).then((dir) => {
done(null, dir);
}).catch((err) => {
done(err, null);
});
});
pool.run(diskManagerTask);
export class DiskManager {
public static scanDirectory(relativeDirectoryName: string, cb: (error: any, result: DirectoryDTO) => void) {
@@ -200,12 +21,14 @@ export class DiskManager {
let directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep);
let absoluteDirectoryName = path.join(ProjectPath.ImageFolder, relativeDirectoryName);
pool.send({
let input = <DiskManagerTask.PoolInput>{
relativeDirectoryName,
directoryName,
directoryParent,
absoluteDirectoryName
}).on('done', (error: any, result: DirectoryDTO) => {
};
let done = (error: any, result: DirectoryDTO) => {
if (error || !result) {
return cb(error, result);
}
@@ -220,9 +43,22 @@ export class DiskManager {
};
addDirs(result);
return cb(error, result);
}).on('error', (error) => {
};
let error = (error) => {
return cb(error, null);
});
};
if (Config.Server.enableThreading == true) {
pool.send(input).on('done', done).on('error', error);
} else {
try {
diskManagerTask(input, done);
} catch (err) {
error(err);
}
}
}
}

View File

@@ -0,0 +1,192 @@
///<reference path="exif.d.ts"/>
import {DirectoryDTO} from "../../common/entities/DirectoryDTO";
import {
CameraMetadata,
GPSMetadata,
ImageSize,
PhotoDTO,
PhotoMetadata,
PositionMetaData
} from "../../common/entities/PhotoDTO";
const LOG_TAG = "[DiskManagerTask]";
export const diskManagerTask = (input: DiskManagerTask.PoolInput, done) => {
const fs = require("fs");
const path = require("path");
const mime = require("mime");
const iptc = require("node-iptc");
const exif_parser = require("exif-parser");
let isImage = (fullPath: string) => {
let imageMimeTypes = [
'image/bmp',
'image/gif',
'image/jpeg',
'image/png',
'image/pjpeg',
'image/tiff',
'image/webp',
'image/x-tiff',
'image/x-windows-bmp'
];
let extension = mime.lookup(fullPath);
return imageMimeTypes.indexOf(extension) !== -1;
};
let loadPhotoMetadata = (fullPath: string): Promise<PhotoMetadata> => {
return new Promise<PhotoMetadata>((resolve: (metadata: PhotoMetadata) => void, reject) => {
fs.readFile(fullPath, function (err, data) {
if (err) {
return reject({file: fullPath, error: err});
}
try {
const exif = exif_parser.create(data).parse();
const iptcData = iptc(data);
const imageSize: ImageSize = {width: exif.imageSize.width, height: exif.imageSize.height};
const cameraData: CameraMetadata = {
ISO: exif.tags.ISO,
model: exif.tags.Modeol,
maker: exif.tags.Make,
fStop: exif.tags.FNumber,
exposure: exif.tags.ExposureTime,
focalLength: exif.tags.FocalLength,
lens: exif.tags.LensModel,
};
const GPS: GPSMetadata = {
latitude: exif.tags.GPSLatitude,
longitude: exif.tags.GPSLongitude,
altitude: exif.tags.GPSAltitude
};
const positionData: PositionMetaData = {
GPSData: GPS,
country: iptcData.country_or_primary_location_name,
state: iptcData.province_or_state,
city: iptcData.city
};
//Decode characters to UTF8
const decode = (s: any) => {
for (let a, b, i = -1, l = (s = s.split("")).length, o = String.fromCharCode, c = "charCodeAt"; ++i < l;
((a = s[i][c](0)) & 0x80) &&
(s[i] = (a & 0xfc) == 0xc0 && ((b = s[i + 1][c](0)) & 0xc0) == 0x80 ?
o(((a & 0x03) << 6) + (b & 0x3f)) : o(128), s[++i] = "")
);
return s.join("");
};
const keywords: string[] = (iptcData.keywords || []).map((s: string) => decode(s));
const creationDate: number = iptcData.date_time ? iptcData.date_time.getTime() : 0;
const metadata: PhotoMetadata = <PhotoMetadata>{
keywords: keywords,
cameraData: cameraData,
positionData: positionData,
size: imageSize,
creationDate: creationDate
};
return resolve(metadata);
} catch (err) {
return reject({file: fullPath, error: err});
}
});
});
};
let parseDir = (directoryInfo: {
relativeDirectoryName: string,
directoryName: string,
directoryParent: string,
absoluteDirectoryName: string
}, maxPhotos: number = null, photosOnly: boolean = false): Promise<DirectoryDTO> => {
return new Promise<DirectoryDTO>((resolve, reject) => {
let promises: Array<Promise<any>> = [];
let directory = <DirectoryDTO>{
name: directoryInfo.directoryName,
path: directoryInfo.directoryParent,
lastUpdate: Date.now(),
directories: [],
photos: []
};
fs.readdir(directoryInfo.absoluteDirectoryName, (err, list) => {
if (err) {
return reject(err);
}
try {
for (let i = 0; i < list.length; i++) {
let file = list[i];
let fullFilePath = path.normalize(path.resolve(directoryInfo.absoluteDirectoryName, file));
if (photosOnly == false && fs.statSync(fullFilePath).isDirectory()) {
let promise = parseDir({
relativeDirectoryName: path.join(directoryInfo.relativeDirectoryName, path.sep),
directoryName: file,
directoryParent: path.join(directoryInfo.relativeDirectoryName, path.sep),
absoluteDirectoryName: fullFilePath
},
5, true
).then((dir) => {
directory.directories.push(dir);
});
promises.push(promise);
} else if (isImage(fullFilePath)) {
let promise = loadPhotoMetadata(fullFilePath).then((photoMetadata) => {
directory.photos.push(<PhotoDTO>{
name: file,
directory: null,
metadata: photoMetadata
});
});
promises.push(promise);
if (maxPhotos != null && promises.length > maxPhotos) {
break;
}
}
}
Promise.all(promises).then(() => {
return resolve(directory);
}).catch((err) => {
return reject({directoryInfo: directoryInfo, error: err});
});
} catch (err) {
return reject({directoryInfo: directoryInfo, error: err});
}
});
});
};
parseDir(input).then((dir) => {
done(null, dir);
}).catch((err) => {
done(err, null);
});
};
export module DiskManagerTask {
export interface PoolInput {
relativeDirectoryName: string;
directoryName: string;
directoryParent: string;
absoluteDirectoryName: string;
}
}

View File

@@ -1,6 +1,6 @@
declare module "node-iptc" {
function e(data):any;
function e(data): any;
module e {
}
@@ -10,6 +10,6 @@ declare module "node-iptc" {
declare module "exif-parser" {
export function create(data):any;
export function create(data): any;
}

View File

@@ -21,16 +21,21 @@ export class PublicRouter {
return next();
});
app.use(_express.static(_path.resolve(__dirname, './../../frontend')));
app.get('/config_inject.js', (req: Request, res: Response) => {
res.render(_path.resolve(__dirname, './../../dist/config_inject.ejs'), res.tpl);
});
app.get(['/', '/login', "/gallery*", "/admin", "/search*"], (req: Request, res: Response) => {
res.sendFile(_path.resolve(__dirname, './../../dist/index.html'));
});
app.use(_express.static(_path.resolve(__dirname, './../../dist')));
app.use('/node_modules', _express.static(_path.resolve(__dirname, './../../node_modules')));
app.use('/common', _express.static(_path.resolve(__dirname, './../../common')));
const renderIndex = (req: Request, res: Response) => {
res.render(_path.resolve(__dirname, './../../frontend/index.ejs'), res.tpl);
res.render(_path.resolve(__dirname, './../../dist/index.html'));
};
app.get(['/', '/login', "/gallery*", "/admin", "/search*"], renderIndex);
}

View File

@@ -70,8 +70,8 @@ export class Utils {
});
}
public static enumToArray(EnumType: any): Array<{key: number;value: string;}> {
let arr: Array<{key: number;value: string;}> = [];
public static enumToArray(EnumType: any): Array<{ key: number; value: string; }> {
let arr: Array<{ key: number; value: string; }> = [];
for (let enumMember in EnumType) {
if (!EnumType.hasOwnProperty(enumMember)) {
continue;

View File

@@ -26,4 +26,5 @@ export interface ServerConfig {
imagesFolder: string;
thumbnail: ThumbnailConfig;
database: DataBaseConfig;
enableThreading:boolean;
}

View File

@@ -24,7 +24,8 @@ export class PrivateConfigClass extends PublicConfigClass {
database: "pigallery2"
}
}
},
enableThreading: true
};
public setDatabaseType(type: DatabaseType) {

View File

@@ -6,10 +6,10 @@ export enum SearchTypes {
}
export class AutoCompleteItem {
constructor(public text:string, public type:SearchTypes) {
constructor(public text: string, public type: SearchTypes) {
}
equals(other:AutoCompleteItem) {
equals(other: AutoCompleteItem) {
return this.text === other.text && this.type === other.type;
}
}

View File

@@ -1,5 +1,5 @@
export class LoginCredential {
constructor(public username:string = "", public password:string = "") {
constructor(public username: string = "", public password: string = "") {
}
}

View File

@@ -1,10 +1,10 @@
import {Error} from "./Error";
export class Message<T> {
public error:Error = null;
public result:T = null;
public error: Error = null;
public result: T = null;
constructor(error:Error, result:T) {
constructor(error: Error, result: T) {
this.error = error;
this.result = result;
}

View File

@@ -2,7 +2,7 @@ import {UserModificationRequest} from "./UserModificationRequest";
export class PasswordChangeRequest extends UserModificationRequest {
constructor(id:number, public oldPassword:string, public newPassword:string) {
constructor(id: number, public oldPassword: string, public newPassword: string) {
super(id);
}
}

View File

@@ -1,4 +1,4 @@
export class UserModificationRequest {
constructor(public id:number) {
constructor(public id: number) {
}
}

View File

@@ -4,16 +4,16 @@ function isFunction(functionToCheck: any) {
}
export class Event<T> {
private handlers: {(data?: T): void;}[] = [];
private handlers: { (data?: T): void; }[] = [];
public on(handler: {(data?: T): void}) {
public on(handler: { (data?: T): void }) {
if (!isFunction(handler)) {
throw new Error("Handler is not a function");
}
this.handlers.push(handler);
}
public off(handler: {(data?: T): void}) {
public off(handler: { (data?: T): void }) {
this.handlers = this.handlers.filter(h => h !== handler);
}

View File

@@ -1,11 +1,11 @@
export class Event2Args<T,M> {
private handlers: { (data?: T,data2?: M): void; }[] = [];
export class Event2Args<T, M> {
private handlers: { (data?: T, data2?: M): void; }[] = [];
public on(handler: { (data?: T,data2?: M): void }) {
public on(handler: { (data?: T, data2?: M): void }) {
this.handlers.push(handler);
}
public off(handler: { (data?: T,data2?: M): void }) {
public off(handler: { (data?: T, data2?: M): void }) {
this.handlers = this.handlers.filter(h => h !== handler);
}
@@ -13,9 +13,9 @@ export class Event2Args<T,M> {
this.handlers = [];
}
public trigger(data?: T,data2?: M) {
public trigger(data?: T, data2?: M) {
if (this.handlers) {
this.handlers.slice(0).forEach(h => h(data,data2));
this.handlers.slice(0).forEach(h => h(data, data2));
}
}
}

View File

@@ -1,24 +1,24 @@
export class EventLimit<T> {
private lastTriggerValue:T = null;
private lastTriggerValue: T = null;
private handlers:Array<EventLimitHandler<T>> = [];
private handlers: Array<EventLimitHandler<T>> = [];
public on(limit:T, handler:{ (data?:T): void }) {
public on(limit: T, handler: { (data?: T): void }) {
this.handlers.push(new EventLimitHandler(limit, handler));
if (this.lastTriggerValue != null) {
this.trigger(this.lastTriggerValue);
}
}
public onSingle(limit:T, handler:{ (data?:T): void }) {
public onSingle(limit: T, handler: { (data?: T): void }) {
this.handlers.push(new SingleFireEventLimitHandler(limit, handler));
if (this.lastTriggerValue != null) {
this.trigger(this.lastTriggerValue);
}
}
public off(limit:T, handler:{ (data?:T): void }) {
public off(limit: T, handler: { (data?: T): void }) {
this.handlers = this.handlers.filter(h => h.handler !== handler && h.limit !== limit);
}
@@ -26,7 +26,7 @@ export class EventLimit<T> {
this.handlers = [];
}
public trigger = (data?:T) => {
public trigger = (data?: T) => {
if (this.handlers) {
this.handlers.slice(0).forEach(h => {
if (h.limit <= data && (h.lastTriggerValue < h.limit || h.lastTriggerValue == null)) {
@@ -41,30 +41,34 @@ export class EventLimit<T> {
}
class EventLimitHandler<T> {
public lastTriggerValue:T = null;
constructor(public limit:T, public handler:{ (data?:T): void }) {
public lastTriggerValue: T = null;
constructor(public limit: T, public handler: { (data?: T): void }) {
}
public fire(data?: T){
public fire(data?: T) {
this.handler(data);
}
public isValid():boolean{
public isValid(): boolean {
return true;
}
}
class SingleFireEventLimitHandler<T> extends EventLimitHandler<T>{
class SingleFireEventLimitHandler<T> extends EventLimitHandler<T> {
public fired = false;
constructor(public limit:T, public handler:{ (data?:T): void }) {
super(limit,handler);
constructor(public limit: T, public handler: { (data?: T): void }) {
super(limit, handler);
}
public fire(data?: T){
if(this.fired == false) {
public fire(data?: T) {
if (this.fired == false) {
this.handler(data);
}
this.fired = true
}
public isValid():boolean{
public isValid(): boolean {
return this.fired === false;
}
}

View File

@@ -5,8 +5,8 @@ import {UserRoles} from "../../../common/entities/UserDTO";
import {Config} from "../../../common/config/public/Config";
@Component({
selector: 'admin',
templateUrl: 'app/admin/admin.component.html',
styleUrls: ['app/admin/admin.component.css']
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.css']
})
export class AdminComponent implements OnInit {
userManagementEnable: boolean = false;

View File

@@ -1,4 +1,4 @@
import {NgModule} from "@angular/core";
import {Injectable, NgModule} from "@angular/core";
import {BrowserModule} from "@angular/platform-browser";
import {FormsModule} from "@angular/forms";
import {HttpModule} from "@angular/http";
@@ -31,6 +31,17 @@ import {GalleryMapLightboxComponent} from "./gallery/map/lightbox/lightbox.map.g
import {ThumbnailManagerService} from "./gallery/thumnailManager.service";
import {OverlayService} from "./gallery/overlay.service";
import {Config} from "../../common/config/public/Config";
import {LAZY_MAPS_API_CONFIG} from "@agm/core/services";
@Injectable()
export class GoogleMapsConfig {
apiKey: string;
constructor() {
this.apiKey = Config.Client.googleApiKey;
}
}
@NgModule({
imports: [
@@ -38,9 +49,7 @@ import {Config} from "../../common/config/public/Config";
FormsModule,
HttpModule,
appRoutes,
AgmCoreModule.forRoot({
apiKey: Config.Client.googleApiKey
})
AgmCoreModule.forRoot()
],
declarations: [AppComponent,
LoginComponent,
@@ -62,6 +71,7 @@ import {Config} from "../../common/config/public/Config";
FrameComponent,
StringifyRole],
providers: [
{provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig},
NetworkService,
UserService,
GalleryCacheService,

View File

@@ -1,5 +1,5 @@
import {ModuleWithProviders} from "@angular/core";
import {Routes, RouterModule} from "@angular/router";
import {RouterModule, Routes} from "@angular/router";
import {LoginComponent} from "./login/login.component";
import {GalleryComponent} from "./gallery/gallery.component";
import {AdminComponent} from "./admin/admin.component";

View File

@@ -8,7 +8,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"><img src="icon_inv.png" style="max-height: 26px; display: inline;"/> PiGallery2</a>
<a class="navbar-brand" href="#"><img src="assets/icon_inv.png" style="max-height: 26px; display: inline;"/> PiGallery2</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">

View File

@@ -6,16 +6,16 @@ import {Config} from "../../../common/config/public/Config";
@Component({
selector: 'app-frame',
templateUrl: 'app/frame/frame.component.html',
templateUrl: './frame.component.html',
providers: [RouterLink],
encapsulation: ViewEncapsulation.Emulated
})
export class FrameComponent {
user: UserDTO;
authenticationRequired:boolean = false;
authenticationRequired: boolean = false;
constructor(private _authService:AuthenticationService) {
constructor(private _authService: AuthenticationService) {
this.user = this._authService.getUser();
this.authenticationRequired = Config.Client.authenticationRequired;
}

View File

@@ -3,7 +3,7 @@ import {Utils} from "../../../common/Utils";
export class IconPhoto {
protected replacementSizeCache: number|boolean = false;
protected replacementSizeCache: number | boolean = false;
constructor(public photo: PhotoDTO) {

View File

@@ -4,10 +4,10 @@
<div class="photo-container">
<div class="photo" *ngIf="thumbnail && thumbnail.available"
<div class="photo" *ngIf="thumbnail && thumbnail.Available"
[style.background-image]="'url('+thumbnail.src+')'"></div>
<span *ngIf="!thumbnail || !thumbnail.available" class="glyphicon glyphicon-folder-open no-image"
<span *ngIf="!thumbnail || !thumbnail.Available" class="glyphicon glyphicon-folder-open no-image"
aria-hidden="true">
</span>

View File

@@ -1,4 +1,4 @@
import {Component, Input, OnInit, OnDestroy, ViewChild, ElementRef} from "@angular/core";
import {Component, ElementRef, Input, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {DirectoryDTO} from "../../../../common/entities/DirectoryDTO";
import {RouterLink} from "@angular/router";
import {Utils} from "../../../../common/Utils";
@@ -7,11 +7,11 @@ import {Thumbnail, ThumbnailManagerService} from "../thumnailManager.service";
@Component({
selector: 'gallery-directory',
templateUrl: 'app/gallery/directory/directory.gallery.component.html',
styleUrls: ['app/gallery/directory/directory.gallery.component.css'],
templateUrl: './directory.gallery.component.html',
styleUrls: ['./directory.gallery.component.css'],
providers: [RouterLink],
})
export class GalleryDirectoryComponent implements OnInit,OnDestroy {
export class GalleryDirectoryComponent implements OnInit, OnDestroy {
@Input() directory: DirectoryDTO;
@ViewChild("dirContainer") container: ElementRef;
thumbnail: Thumbnail = null;

View File

@@ -9,8 +9,8 @@ import {Config} from "../../../common/config/public/Config";
@Component({
selector: 'gallery',
templateUrl: 'app/gallery/gallery.component.html',
styleUrls: ['app/gallery/gallery.component.css']
templateUrl: './gallery.component.html',
styleUrls: ['./gallery.component.css']
})
export class GalleryComponent implements OnInit {
@@ -19,7 +19,7 @@ export class GalleryComponent implements OnInit {
public showSearchBar: boolean = true;
constructor(private _galleryService: GalleryService,
constructor(public _galleryService: GalleryService,
private _authService: AuthenticationService,
private _router: Router,
private _route: ActivatedRoute) {
@@ -60,8 +60,6 @@ export class GalleryComponent implements OnInit {
});
}
onLightboxLastElement() {

View File

@@ -18,7 +18,7 @@ export class GalleryService {
this.content = new ContentWrapper();
}
lastRequest: {directory: string} = {
lastRequest: { directory: string } = {
directory: null
};

View File

@@ -4,20 +4,20 @@ export class GridRowBuilder {
private photoRow: Array<PhotoDTO> = [];
private photoIndex:number = 0; //index of the last pushed photo to the photoRow
private photoIndex: number = 0; //index of the last pushed photo to the photoRow
constructor(private photos: Array<PhotoDTO>, private startIndex: number, private photoMargin: number, private containerWidth: number) {
this.photoIndex = startIndex;
}
public addPhotos(number:number) {
public addPhotos(number: number) {
for (let i = 0; i < number; i++) {
this.addPhoto();
}
}
public addPhoto():boolean {
public addPhoto(): boolean {
if (this.photoIndex + 1 > this.photos.length) {
return false;
}
@@ -26,7 +26,7 @@ export class GridRowBuilder {
return true;
}
public removePhoto():boolean {
public removePhoto(): boolean {
if (this.photoIndex - 1 < this.startIndex) {
return false;
}
@@ -39,7 +39,7 @@ export class GridRowBuilder {
return this.photoRow;
}
public adjustRowHeightBetween(minHeight:number, maxHeight:number) {
public adjustRowHeightBetween(minHeight: number, maxHeight: number) {
while (this.calcRowHeight() > maxHeight && this.addPhoto() === true) { //row too high -> add more images
}
@@ -52,7 +52,7 @@ export class GridRowBuilder {
}
}
public calcRowHeight():number {
public calcRowHeight(): number {
let width = 0;
for (let i = 0; i < this.photoRow.length; i++) {
width += ((this.photoRow[i].metadata.size.width) / (this.photoRow[i].metadata.size.height)); //summing up aspect ratios

View File

@@ -20,8 +20,8 @@ import {Config} from "../../../../common/config/public/Config";
@Component({
selector: 'gallery-grid',
templateUrl: 'app/gallery/grid/grid.gallery.component.html',
styleUrls: ['app/gallery/grid/grid.gallery.component.css'],
templateUrl: './grid.gallery.component.html',
styleUrls: ['./grid.gallery.component.css'],
})
export class GalleryGridComponent implements OnChanges, AfterViewInit {
@@ -52,9 +52,9 @@ export class GalleryGridComponent implements OnChanges, AfterViewInit {
this.updateContainerWidth();
this.sortPhotos();
this.mergeNewPhotos();
setImmediate(() => {
setTimeout(() => {
this.renderPhotos();
});
}, 0);
}
@HostListener('window:resize')
@@ -82,9 +82,9 @@ export class GalleryGridComponent implements OnChanges, AfterViewInit {
this.updateContainerWidth();
this.sortPhotos();
this.clearRenderedPhotos();
setImmediate(() => {
setTimeout(() => {
this.renderPhotos();
});
}, 0);
this.isAfterViewInit = true;
}

View File

@@ -2,12 +2,12 @@ import {Component, Input} from "@angular/core";
@Component({
selector: 'gallery-grid-photo-loading',
templateUrl: 'app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.html',
styleUrls: ['app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.css'],
templateUrl: './loading.photo.grid.gallery.component.html',
styleUrls: ['./loading.photo.grid.gallery.component.css'],
})
export class GalleryPhotoLoadingComponent {
@Input() animate:boolean;
@Input() animate: boolean;
}

View File

@@ -1,7 +1,7 @@
<div #photoContainer class="photo-container" (mouseover)="hover()" (mouseout)="mouseOut()">
<img #img [src]="thumbnail.src" [hidden]="!thumbnail.available">
<img #img [src]="thumbnail.Src" [hidden]="!thumbnail.Available">
<gallery-grid-photo-loading [animate]="thumbnail.loading" *ngIf="!thumbnail.available">
<gallery-grid-photo-loading [animate]="thumbnail.loading" *ngIf="!thumbnail.Available">
</gallery-grid-photo-loading>
<!--Info box -->

View File

@@ -8,8 +8,8 @@ import {Config} from "../../../../../common/config/public/Config";
@Component({
selector: 'gallery-grid-photo',
templateUrl: 'app/gallery/grid/photo/photo.grid.gallery.component.html',
styleUrls: ['app/gallery/grid/photo/photo.grid.gallery.component.css'],
templateUrl: './photo.grid.gallery.component.html',
styleUrls: ['./photo.grid.gallery.component.css'],
providers: [RouterLink],
})
export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {

View File

@@ -17,8 +17,8 @@ import {Subscription} from "rxjs";
@Component({
selector: 'gallery-lightbox',
styleUrls: ['app/gallery/lightbox/lightbox.gallery.component.css'],
templateUrl: 'app/gallery/lightbox/lightbox.gallery.component.html',
styleUrls: ['./lightbox.gallery.component.css'],
templateUrl: './lightbox.gallery.component.html',
})
export class GalleryLightboxComponent {
@Output('onLastElement') onLastElement = new EventEmitter();
@@ -26,19 +26,19 @@ export class GalleryLightboxComponent {
public navigation = {hasPrev: true, hasNext: true};
public photoDimension: Dimension = <Dimension>{top: 0, left: 0, width: 0, height: 0};
public lightboxDimension: Dimension = <Dimension>{top: 0, left: 0, width: 0, height: 0};
private transition: string = "";
public transition: string = "";
public blackCanvasOpacity: any = 0;
private activePhoto: GalleryPhotoComponent;
public activePhoto: GalleryPhotoComponent;
private gridPhotoQL: QueryList<GalleryPhotoComponent>;
private visible = false;
public visible = false;
private changeSubscription: Subscription = null;
@ViewChild("root") elementRef: ElementRef;
constructor(private fullScreenService: FullScreenService, private changeDetector: ChangeDetectorRef, private overlayService: OverlayService) {
constructor(public fullScreenService: FullScreenService, private changeDetector: ChangeDetectorRef, private overlayService: OverlayService) {
}

View File

@@ -3,8 +3,8 @@ import {GridPhoto} from "../../grid/GridPhoto";
@Component({
selector: 'gallery-lightbox-photo',
styleUrls: ['app/gallery/lightbox/photo/photo.lightbox.gallery.component.css'],
templateUrl: 'app/gallery/lightbox/photo/photo.lightbox.gallery.component.html'
styleUrls: ['./photo.lightbox.gallery.component.css'],
templateUrl: './photo.lightbox.gallery.component.html'
})
export class GalleryLightboxPhotoComponent implements OnChanges {

View File

@@ -8,8 +8,8 @@ import {IconPhoto} from "../../IconPhoto";
@Component({
selector: 'gallery-map-lightbox',
styleUrls: ['app/gallery/map/lightbox/lightbox.map.gallery.component.css'],
templateUrl: 'app/gallery/map/lightbox/lightbox.map.gallery.component.html',
styleUrls: ['./lightbox.map.gallery.component.css'],
templateUrl: './lightbox.map.gallery.component.html',
})
export class GalleryMapLightboxComponent implements OnChanges {
@@ -17,9 +17,9 @@ export class GalleryMapLightboxComponent implements OnChanges {
private startPosition = null;
public lightboxDimension: Dimension = <Dimension>{top: 0, left: 0, width: 0, height: 0};
public mapDimension: Dimension = <Dimension>{top: 0, left: 0, width: 0, height: 0};
private visible = false;
private opacity = 1.0;
mapPhotos: Array<{latitude: number, longitude: number, iconUrl?: string, thumbnail: IconThumbnail}> = [];
public visible = false;
public opacity = 1.0;
mapPhotos: Array<{ latitude: number, longitude: number, iconUrl?: string, thumbnail: IconThumbnail }> = [];
mapCenter = {latitude: 0, longitude: 0};
@ViewChild("root") elementRef: ElementRef;
@@ -27,7 +27,7 @@ export class GalleryMapLightboxComponent implements OnChanges {
@ViewChild(AgmMap) map: AgmMap;
constructor(private fullScreenService: FullScreenService, private thumbnailService: ThumbnailManagerService) {
constructor(public fullScreenService: FullScreenService, private thumbnailService: ThumbnailManagerService) {
}
@@ -95,7 +95,7 @@ export class GalleryMapLightboxComponent implements OnChanges {
return p.metadata && p.metadata.positionData && p.metadata.positionData.GPSData;
}).map(p => {
let th = this.thumbnailService.getIcon(new IconPhoto(p));
let obj: {latitude: number, longitude: number, iconUrl?: string, thumbnail: IconThumbnail} = {
let obj: { latitude: number, longitude: number, iconUrl?: string, thumbnail: IconThumbnail } = {
latitude: p.metadata.positionData.GPSData.latitude,
longitude: p.metadata.positionData.GPSData.longitude,
thumbnail: th

View File

@@ -1,18 +1,18 @@
import {Component, OnChanges, Input, ViewChild, ElementRef} from "@angular/core";
import {Component, ElementRef, Input, OnChanges, ViewChild} from "@angular/core";
import {PhotoDTO} from "../../../../common/entities/PhotoDTO";
import {IRenderable, Dimension} from "../../model/IRenderable";
import {Dimension, IRenderable} from "../../model/IRenderable";
import {GalleryMapLightboxComponent} from "./lightbox/lightbox.map.gallery.component";
@Component({
selector: 'gallery-map',
templateUrl: 'app/gallery/map/map.gallery.component.html',
styleUrls: ['app/gallery/map/map.gallery.component.css']
templateUrl: './map.gallery.component.html',
styleUrls: ['./map.gallery.component.css']
})
export class GalleryMapComponent implements OnChanges, IRenderable {
@Input() photos: Array<PhotoDTO>;
@ViewChild(GalleryMapLightboxComponent) mapLightbox: GalleryMapLightboxComponent;
mapPhotos: Array<{latitude: number, longitude: number}> = [];
mapPhotos: Array<{ latitude: number, longitude: number }> = [];
mapCenter = {latitude: 0, longitude: 0};
@ViewChild("map") map: ElementRef;

View File

@@ -4,7 +4,7 @@ import {RouterLink} from "@angular/router";
@Component({
selector: 'gallery-navbar',
templateUrl: 'app/gallery/navigator/navigator.gallery.component.html',
templateUrl: './navigator.gallery.component.html',
providers: [RouterLink],
})
export class GalleryNavigatorComponent implements OnChanges {

View File

@@ -7,10 +7,10 @@ import {Message} from "../../../../common/entities/Message";
export class AutoCompleteService {
constructor(private _networkService:NetworkService) {
constructor(private _networkService: NetworkService) {
}
public autoComplete(text:string):Promise<Message<Array<AutoCompleteItem> >> {
public autoComplete(text: string): Promise<Message<Array<AutoCompleteItem>>> {
return this._networkService.getJson("/autocomplete/" + text);
}

View File

@@ -2,7 +2,7 @@
<form class="navbar-form" role="search" #SearchForm="ngForm">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search" (keyup)="onSearchChange($event)"
(blur)="onFocusLost($event)" (focus)="onFocus($evnet)" [(ngModel)]="searchText" #name="ngModel"
(blur)="onFocusLost()" (focus)="onFocus()" [(ngModel)]="searchText" #name="ngModel"
ngControl="search"
name="srch-term" id="srch-term" autocomplete="off">

View File

@@ -8,14 +8,14 @@ import {Config} from "../../../../common/config/public/Config";
@Component({
selector: 'gallery-search',
templateUrl: 'app/gallery/search/search.gallery.component.html',
styleUrls: ['app/gallery/search/search.gallery.component.css'],
templateUrl: './search.gallery.component.html',
styleUrls: ['./search.gallery.component.css'],
providers: [AutoCompleteService, RouterLink]
})
export class GallerySearchComponent {
autoCompleteItems: Array<AutoCompleteRenderItem> = [];
private searchText: string = "";
public searchText: string = "";
private cache = {
lastAutocomplete: "",
lastInstantSearch: ""

View File

@@ -8,8 +8,8 @@ import {ErrorCodes} from "../../../common/entities/Error";
@Component({
selector: 'login',
templateUrl: 'app/login/login.component.html',
styleUrls: ['app/login/login.component.css'],
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
})
export class LoginComponent implements OnInit {
loginCredential: LoginCredential;

View File

@@ -1,5 +1,5 @@
export interface IRenderable {
getDimension():Dimension;
getDimension(): Dimension;
}
export interface Dimension {

View File

@@ -10,6 +10,10 @@ class MockUserService {
public login(credential: LoginCredential) {
return Promise.resolve(new Message<UserDTO>(null, <UserDTO>{name: "testUserName"}))
}
public async getSessionUser() {
return null;
}
}
describe('AuthenticationService', () => {

View File

@@ -1,5 +1,5 @@
import {Injectable} from "@angular/core";
import {Http, Headers, RequestOptions} from "@angular/http";
import {Headers, Http, RequestOptions} from "@angular/http";
import {Message} from "../../../../common/entities/Message";
import "rxjs/Rx";
@@ -8,10 +8,10 @@ export class NetworkService {
_baseUrl = "/api";
constructor(protected _http:Http) {
constructor(protected _http: Http) {
}
private callJson<T>(method:string, url:string, data:any = {}):Promise<T> {
private callJson<T>(method: string, url: string, data: any = {}): Promise<T> {
let body = JSON.stringify(data);
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
@@ -29,24 +29,24 @@ export class NetworkService {
.catch(NetworkService.handleError);
}
public postJson<T>(url:string, data:any = {}):Promise<T> {
public postJson<T>(url: string, data: any = {}): Promise<T> {
return this.callJson("post", url, data);
}
public putJson<T>(url:string, data:any = {}):Promise<T> {
public putJson<T>(url: string, data: any = {}): Promise<T> {
return this.callJson("put", url, data);
}
public getJson<T>(url:string):Promise<T> {
public getJson<T>(url: string): Promise<T> {
return this.callJson("get", url);
}
public deleteJson<T>(url:string):Promise<T> {
public deleteJson<T>(url: string): Promise<T> {
return this.callJson("delete", url);
}
private static handleError(error:any) {
private static handleError(error: any) {
// TODO: in a real world app do smthing better
// instead of just logging it to the console
console.error(error);

View File

@@ -8,19 +8,19 @@ export class NotificationService {
}
public showException(message:string) {
public showException(message: string) {
}
public showError(message:string) {
public showError(message: string) {
}
public showWarn(message:string) {
public showWarn(message: string) {
}
public showInfo(message:string) {
public showInfo(message: string) {
}

View File

@@ -4,7 +4,7 @@ import {UserRoles} from "../../../common/entities/UserDTO";
@Pipe({name: 'stringifyRole'})
export class StringifyRole implements PipeTransform {
transform(role:string):number {
transform(role: string): number {
return UserRoles[role];
}
}

View File

@@ -49,7 +49,8 @@
aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">Modal title</h4>
</div>
<form (ngSubmit)="onSubmit()" #NewUserForm="ngForm">
<form #NewUserForm="ngForm">
<div class="modal-body">
<input type="text" class="form-control" placeholder="Username" autofocus
[(ngModel)]="newUser.name" name="name" required>

View File

@@ -8,15 +8,15 @@ import {UserManagerSettingsService} from "./usermanager.settings.service";
@Component({
selector: 'settings-usermanager',
templateUrl: 'app/settings/usermanager/usermanager.settings.component.html',
styleUrls: ['app/settings/usermanager/usermanager.settings.component.css'],
templateUrl: './usermanager.settings.component.html',
styleUrls: ['./usermanager.settings.component.css'],
providers: [UserManagerSettingsService],
})
export class UserMangerSettingsComponent implements OnInit {
private newUser = <UserDTO>{};
private userRoles: Array<any> = [];
private users: Array<UserDTO> = [];
public newUser = <UserDTO>{};
public userRoles: Array<any> = [];
public users: Array<UserDTO> = [];
constructor(private _authService: AuthenticationService, private _router: Router, private _userSettings: UserManagerSettingsService) {
}

View File

@@ -7,7 +7,7 @@ import {Message} from "../../../../common/entities/Message";
export class UserManagerSettingsService {
constructor(private _networkService:NetworkService) {
constructor(private _networkService: NetworkService) {
}
public createUser(user: UserDTO): Promise<Message<string>> {

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,4 @@
var ServerInject = {
user: <%- JSON.stringify(user); %>,
ConfigInject: <%- JSON.stringify(clientConfig); %>
}

View File

@@ -0,0 +1,3 @@
export const environment = {
production: true
};

View File

@@ -0,0 +1,8 @@
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: false
};

View File

@@ -1,48 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/"/>
<meta charset="UTF-8">
<title>PiGallery2</title>
<link rel="shortcut icon" href="icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7"
crossorigin="anonymous">
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script>
var ServerInject = {
user: <%- JSON.stringify(user); %>,
ConfigInject: <%- JSON.stringify(clientConfig); %>
}
</script>
<script src="systemjs.config.js"></script>
<script>
System.import('').catch(function (err) {
console.error(err);
});
</script>
</head>
<body style="overflow-y: scroll">
<pi-gallery2-app>Loading...</pi-gallery2-app>
<script
src="https://code.jquery.com/jquery-2.2.3.min.js"
integrity="sha256-a23g1Nt4dtEYOj7bR+vTu7+T8VP13humZFBJNIYoEJo="
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS"
crossorigin="anonymous"></script>
</body>
</html>

28
frontend/index.html Normal file
View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/"/>
<meta charset="UTF-8">
<title>PiGallery2</title>
<link rel="shortcut icon" href="assets/icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7"
crossorigin="anonymous">
<script type="text/javascript" src="config_inject.js"></script>
</head>
<body style="overflow-y: scroll">
<pi-gallery2-app>Loading...</pi-gallery2-app>
<script
src="https://code.jquery.com/jquery-2.2.3.min.js"
integrity="sha256-a23g1Nt4dtEYOj7bR+vTu7+T8VP13humZFBJNIYoEJo="
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS"
crossorigin="anonymous"></script>
</body>
</html>

61
frontend/polyfills.ts Normal file
View File

@@ -0,0 +1,61 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following to support `@angular/animation`. */
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/** Evergreen browsers require these. **/
import "core-js/es6/reflect";
import "core-js/es7/reflect";
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/***************************************************************************************************
* Zone JS is required by Angular itself.
*/
import "zone.js/dist/zone"; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/**
* Date, currency, decimal and percent pipes.
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
*/
import 'intl'; // Run `npm install --save intl`.
/**
* Need to import at least one locale-data with intl.
*/
// import 'intl/locale-data/jsonp/en';

1
frontend/styles.css Normal file
View File

@@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View File

@@ -1,11 +0,0 @@
/**
* Add barrels and stuff
* Adjust as necessary for your application needs.
*/
// (function (global) {
// System.config({
// packages: {
// // add packages here
// }
// });
// })(this);

View File

@@ -1,48 +0,0 @@
/**
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function (global) {
System.config({
paths: {
// paths serve as alias
'npm:': 'node_modules/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
app: '',
// angular bundles
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
// other libraries
'rxjs': 'npm:rxjs',
'@agm/core': 'npm:@agm/core/core.umd.js',
'ng2-cookies': 'npm:ng2-cookies/ng2-cookies',
'typeconfig': 'npm:typeconfig'
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.js',
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
},
"angular2-google-maps/core": {
"defaultExtension": "js",
"main": "index.js"
}
}
});
})(this);

30
frontend/test.ts Normal file
View File

@@ -0,0 +1,30 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import "zone.js/dist/long-stack-trace-zone";
import "zone.js/dist/proxy.js";
import "zone.js/dist/sync-test";
import "zone.js/dist/jasmine-patch";
import "zone.js/dist/async-test";
import "zone.js/dist/fake-async-test";
import {getTestBed} from "@angular/core/testing";
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "@angular/platform-browser-dynamic/testing";
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare const __karma__: any;
declare const require: any;
// Prevent Karma from running prematurely.
__karma__.loaded = function () {
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
// Finally, start Karma to run the tests.
__karma__.start();

View File

@@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "es2015",
"baseUrl": "",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,20 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"module": "commonjs",
"target": "es5",
"baseUrl": "",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

5
frontend/typings.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}

View File

@@ -1,98 +0,0 @@
// /*global jasmine, __karma__, window*/
Error.stackTraceLimit = 0; // "No stacktrace"" is usually best for app testing.
// Uncomment to get full stacktrace output. Sometimes helpful, usually not.
// Error.stackTraceLimit = Infinity; //
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
// builtPaths: root paths for output ("built") files
// get from karma.config.js, then prefix with '/base/' (default is 'app/')
var builtPaths = (__karma__.config.builtPaths || ['app/'])
.map(function (p) {
return '/base/' + p;
});
__karma__.loaded = function () {
};
function isJsFile(path) {
return path.slice(-3) == '.js';
}
function isSpecFile(path) {
return /\.spec\.(.*\.)?js$/.test(path);
}
// Is a "built" file if is JavaScript file in one of the "built" folders
function isBuiltFile(path) {
return isJsFile(path) &&
builtPaths.reduce(function (keep, bp) {
return keep || (path.substr(0, bp.length) === bp);
}, false);
}
var allSpecFiles = Object.keys(window.__karma__.files)
.filter(isSpecFile)
.filter(isBuiltFile);
System.config({
baseURL: 'base',
// Extend usual application package list with test folder
packages: {'testing': {main: 'index.js', defaultExtension: 'js'}},
// Assume npm: is set in `paths` in systemjs.config
// Map the angular testing umd bundles
map: {
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
'@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
'@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
'@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',
},
});
System.import('frontend/systemjs.config.js')
.then(importSystemJsExtras)
.then(initTestBed)
.then(initTesting);
/** Optional SystemJS configuration extras. Keep going w/o it */
function importSystemJsExtras() {
return System.import('frontend/systemjs.config.extras.js')
.catch(function (reason) {
console.log(
'Warning: System.import could not load the optional "systemjs.config.extras.js". Did you omit it by accident? Continuing without it.'
);
console.log(reason);
});
}
function initTestBed() {
return Promise.all([
System.import('@angular/core/testing'),
System.import('@angular/platform-browser-dynamic/testing')
])
.then(function (providers) {
var coreTesting = providers[0];
var browserTesting = providers[1];
coreTesting.TestBed.initTestEnvironment(
browserTesting.BrowserDynamicTestingModule,
browserTesting.platformBrowserDynamicTesting());
})
}
// Import all spec files and start karma
function initTesting() {
return Promise.all(
allSpecFiles.map(function (moduleName) {
return System.import(moduleName);
})
)
.then(__karma__.start, __karma__.error);
}

View File

@@ -1,106 +1,33 @@
module.exports = function(config) {
var appBase = 'frontend/'; // transpiled app JS and map files
var appSrcBase = 'frontend/'; // app source TS files
var commonBase = 'common/'; // transpiled app JS and map files
var commonSrcBase = 'common/'; // app source TS files
var appAssets = 'base/'; // component assets fetched by Angular's compiler
// Testing helpers (optional) are conventionally in a folder called `testing`
var testingBase = 'testing/'; // transpiled test JS and map files
var testingSrcBase = 'testing/'; // test source TS files
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-phantomjs-launcher'),
require('karma-jasmine-html-reporter')
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
client: {
builtPaths: [appBase, commonBase, testingBase], // add more spec base paths as needed
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
files: [
// Polyfills
'node_modules/core-js/client/shim.js',
'node_modules/reflect-metadata/Reflect.js',
// System.js for module loading
'node_modules/systemjs/dist/system.js',
// zone.js
'node_modules/zone.js/dist/zone.js',
'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/proxy.js',
'node_modules/zone.js/dist/sync-test.js',
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js',
// RxJs
{pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false},
{pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false},
//Other libs
{pattern: 'node_modules/ng2-cookies/**/*.js', included: false, watched: false},
{pattern: 'node_modules/typeconfig/**/*.js', included: false, watched: false},
// Paths loaded via module imports:
// Angular itself
{pattern: 'node_modules/@angular/**/*.js', included: false, watched: false},
{pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false},
{pattern: 'systemjs.config.js', included: false, watched: false},
{pattern: 'systemjs.config.extras.js', included: false, watched: false},
'karma-test-shim.js', // optionally extend SystemJS mapping e.g., with barrels
// transpiled application & spec code paths loaded via module imports
{pattern: appBase + '**/*.js', included: false, watched: true},
{pattern: commonBase + '**/*.js', included: false, watched: true},
{pattern: testingBase + '**/*.js', included: false, watched: true},
// Asset (HTML & CSS) paths loaded via Angular's component compiler
// (these paths need to be rewritten, see proxies section)
{pattern: appBase + '**/*.html', included: false, watched: true},
{pattern: appBase + '**/*.css', included: false, watched: true},
{pattern: commonBase + '**/*.html', included: false, watched: true},
{pattern: commonBase + '**/*.css', included: false, watched: true},
// Paths for debugging with source maps in dev tools
{pattern: appSrcBase + '**/*.ts', included: false, watched: false},
{pattern: commonSrcBase + '**/*.ts', included: false, watched: false},
{pattern: appBase + '**/*.js.map', included: false, watched: false},
{pattern: commonBase + '**/*.js.map', included: false, watched: false},
{pattern: testingSrcBase + '**/*.ts', included: false, watched: false},
{pattern: testingBase + '**/*.js.map', included: false, watched: false}
],
// Proxied base paths for loading assets
proxies: {
// required for component assets fetched by Angular's compiler
"/app/": appAssets
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
exclude: [],
preprocessors: {},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['PhantomJS'],
singleRun: false
})
}
});
};

View File

@@ -7,10 +7,12 @@
"license": "MIT",
"main": "./backend/server.js",
"scripts": {
"build": "tsc",
"pretest": "tsc",
"test": "karma start karma.conf.js --single-run && mocha --recursive test/backend/unit",
"start": "node ./backend/server"
"build": "ng build",
"test": "ng test --single-run && mocha --recursive test/backend/unit",
"start": "node ./backend/server",
"ng": "ng",
"lint": "ng lint",
"e2e": "ng e2e"
},
"repository": {
"type": "git",
@@ -37,6 +39,7 @@
"express-session": "^1.15.3",
"express-winston": "^2.4.0",
"flat-file-db": "^1.0.0",
"intl": "^1.2.5",
"jimp": "^0.2.28",
"mime": "^1.3.6",
"mocha": "^3.4.2",
@@ -54,6 +57,9 @@
"zone.js": "^0.8.11"
},
"devDependencies": {
"@angular/cli": "1.1.1",
"@angular/compiler-cli": "^4.0.0",
"@angular/language-service": "^4.0.0",
"@types/express": "^4.0.35",
"@types/express-session": "1.15.0",
"@types/jasmine": "^2.5.51",
@@ -62,12 +68,16 @@
"@types/sharp": "^0.17.2",
"@types/winston": "^2.3.3",
"chai": "^4.0.1",
"codelyzer": "~3.0.1",
"ejs-loader": "^0.3.0",
"gulp": "^3.9.1",
"gulp-typescript": "^3.1.7",
"gulp-zip": "^4.0.0",
"jasmine-core": "^2.6.2",
"jasmine-spec-reporter": "~4.1.0",
"karma": "^1.7.0",
"karma-cli": "^1.0.1",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "^1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-phantomjs-launcher": "^1.0.4",
@@ -81,6 +91,7 @@
"rimraf": "^2.6.1",
"run-sequence": "^1.2.2",
"ts-helpers": "^1.1.2",
"ts-node": "~3.0.4",
"tslint": "^5.4.2",
"typescript": "^2.3.4"
},

28
protractor.conf.js Normal file
View File

@@ -0,0 +1,28 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./test/e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'test/e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

14
test/e2e/app.e2e-spec.ts Normal file
View File

@@ -0,0 +1,14 @@
import {TestProjectPage} from "./app.po";
describe('test-project App', () => {
let page: TestProjectPage;
beforeEach(() => {
page = new TestProjectPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!!');
});
});

11
test/e2e/app.po.ts Normal file
View File

@@ -0,0 +1,11 @@
import {browser, by, element} from "protractor";
export class TestProjectPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"node"
]
}
}

View File

@@ -1,50 +0,0 @@
// @AngularClass
/*
* When testing with webpack and ES6, we have to do some extra
* things get testing to work right. Because we are gonna write test
* in ES6 to, we have to compile those as well. That's handled in
* karma.conf.js with the karma-webpack plugin. This is the entry
* file for webpack test. Just like webpack will create a bundle.js
* file for our client, when we run test, it well compile and bundle them
* all here! Crazy huh. So we need to do some setup
*/
Error.stackTraceLimit = Infinity;
require('core-js');
// Typescript emit helpers polyfill
require('ts-helpers');
require('zone.js/dist/zone.js');
require('zone.js/dist/long-stack-trace-zone.js');
require('zone.js/dist/jasmine-patch.js');
require('zone.js/dist/async-test');
var testing = require('@angular/core/testing');
var browser = require('@angular/platform-browser-dynamic/testing');
testing.setBaseTestProviders(
browser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
browser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
);
Object.assign(global, testing);
/*
Ok, this is kinda crazy. We can use the the context method on
require that webpack created in order to tell webpack
what files we actually want to require or import.
Below, context will be an function/object with file names as keys.
using that regex we are saying look in ./src/app and ./test then find
any file that ends with spec.js and get its path. By passing in true
we say do this recursively
*/
var testContext = require.context('./../frontend', true, /\.spec\.ts/);
// get all the files, for each file, call the context function
// that will require the file and load it up here. Context will
// loop and require those spec files here
function requireAll(requireContext) {
return requireContext.keys().map(requireContext);
}
var modules = requireAll(testContext);
// requires and returns all modules that match

View File

@@ -1,52 +0,0 @@
// @AngularClass
/*
* When testing with webpack and ES6, we have to do some extra
* things get testing to work right. Because we are gonna write test
* in ES6 to, we have to compile those as well. That's handled in
* karma.conf.js with the karma-webpack plugin. This is the entry
* file for webpack test. Just like webpack will create a bundle.js
* file for our client, when we run test, it well compile and bundle them
* all here! Crazy huh. So we need to do some setup
*/
Error.stackTraceLimit = Infinity;
// Prefer CoreJS over the polyfills above
require('core-js');
// Typescript emit helpers polyfill
require('ts-helpers');
require('zone.js/dist/zone.js');
require('zone.js/dist/long-stack-trace-zone.js');
require('zone.js/dist/jasmine-patch.js');
require('zone.js/dist/async-test');
var testing = require('@angular/core/testing');
var browser = require('@angular/platform-browser-dynamic/testing');
testing.setBaseTestProviders(
browser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
browser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
);
Object.assign(global, testing);
/*
Ok, this is kinda crazy. We can use the the context method on
require that webpack created in order to tell webpack
what files we actually want to require or import.
Below, context will be an function/object with file names as keys.
using that regex we are saying look in ./src/app and ./test then find
any file that ends with spec.js and get its path. By passing in true
we say do this recursively
*/
var testContext = require.context('./../frontend', true, /\.ts/);
// get all the files, for each file, call the context function
// that will require the file and load it up here. Context will
// loop and require those spec files here
function requireAll(requireContext) {
return requireContext.keys().map(requireContext);
}
var modules = requireAll(testContext);
// requires and returns all modules that match

View File

@@ -1,71 +0,0 @@
{
"rulesDirectory": [
"node_modules/ng2lint/dist/src"
],
"rules": {
"component-selector-name": [true, "kebab-case"],
"component-selector-type": [true, "element"],
"host-parameter-decorator": true,
"input-parameter-decorator": true,
"output-parameter-decorator": true,
"attribute-parameter-decorator": false,
"input-property-directive": true,
"output-property-directive": true,
"class-name": true,
"curly": false,
"eofline": true,
"indent": [
true,
"spaces"
],
"max-line-length": [
true,
100
],
"member-ordering": [
true,
"public-before-private",
"static-before-instance",
"variables-before-functions"
],
"experimentalDecorators":true,
"no-arg": true,
"no-construct": true,
"no-duplicate-key": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-eval": true,
"trailing-comma": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-unused-variable": false,
"no-unreachable": true,
"no-use-before-declare": true,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"quotemark": [
true,
"single"
],
"semicolon": true,
"triple-equals": [
true,
"allow-null-check"
],
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

View File

@@ -1,22 +1,18 @@
{
"compileOnSave": true,
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"suppressImplicitAnyIndexErrors": false,
"lib": [
"es2015",
"dom",
"es2015.promise"
]
},
"exclude": [
"node_modules"
],
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2016",
"dom"
]
}
}

135
tslint.json Normal file
View File

@@ -0,0 +1,135 @@
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs"
],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"typeof-compare": true,
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true,
"no-access-missing-member": true,
"templates-use-public": true,
"invoke-injectable": true
}
}