1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-12-25 02:04:15 +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

@ -1,33 +1,33 @@
import * as winston from "winston";
declare module 'winston' {
interface LoggerInstance {
logFileName: string;
logFilePath: string;
}
interface LoggerInstance {
logFileName: string;
logFilePath: string;
}
}
export const winstonSettings = {
transports: [
new winston.transports.Console({
level: 'silly',
handleExceptions: true,
json: false,
colorize: true,
timestamp: function () {
return (new Date()).toLocaleString();
},
label: "innerLabel",
formatter: (options) => {
// Return string will be passed to logger.
return options.timestamp() + '[' + winston['config']['colorize'](options.level, options.level.toUpperCase()) + '] ' +
(undefined !== options.message ? options.message : '') +
(options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : '' );
},
debugStdout: true
})
],
exitOnError: false
transports: [
new winston.transports.Console({
level: 'silly',
handleExceptions: true,
json: false,
colorize: true,
timestamp: function () {
return (new Date()).toLocaleString();
},
label: "innerLabel",
formatter: (options) => {
// Return string will be passed to logger.
return options.timestamp() + '[' + winston['config']['colorize'](options.level, options.level.toUpperCase()) + '] ' +
(undefined !== options.message ? options.message : '') +
(options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : '' );
},
debugStdout: true
})
],
exitOnError: false
};
export const Logger = new winston.Logger(winstonSettings);
export const Logger = new winston.Logger(winstonSettings);

View File

@ -2,23 +2,23 @@ import * as path from "path";
import {Config} from "../common/config/private/Config";
class ProjectPathClass {
public Root: string;
public ImageFolder: string;
public ThumbnailFolder: string;
public Root: string;
public ImageFolder: string;
public ThumbnailFolder: string;
isAbsolutePath(pathStr: string) {
return path.resolve(pathStr) === path.normalize(pathStr);
}
isAbsolutePath(pathStr: string) {
return path.resolve(pathStr) === path.normalize(pathStr);
}
normalizeRelative(pathStr: string) {
return path.join(pathStr, path.sep);
}
normalizeRelative(pathStr: string) {
return path.join(pathStr, path.sep);
}
constructor() {
this.Root = path.join(__dirname, "/../");
this.ImageFolder = this.isAbsolutePath(Config.Server.imagesFolder) ? Config.Server.imagesFolder : path.join(this.Root, Config.Server.imagesFolder);
this.ThumbnailFolder = this.isAbsolutePath(Config.Server.thumbnail.folder) ? Config.Server.thumbnail.folder : path.join(this.Root, Config.Server.thumbnail.folder);
}
constructor() {
this.Root = path.join(__dirname, "/../");
this.ImageFolder = this.isAbsolutePath(Config.Server.imagesFolder) ? Config.Server.imagesFolder : path.join(this.Root, Config.Server.imagesFolder);
this.ThumbnailFolder = this.isAbsolutePath(Config.Server.thumbnail.folder) ? Config.Server.thumbnail.folder : path.join(this.Root, Config.Server.thumbnail.folder);
}
}
export let ProjectPath = new ProjectPathClass();
export const ProjectPath = new ProjectPathClass();

View File

@ -17,133 +17,133 @@ const LOG_TAG = "[GalleryMWs]";
export class GalleryMWs {
public static listDirectory(req: Request, res: Response, next: NextFunction) {
let directoryName = req.params.directory || "/";
let absoluteDirectoryName = path.join(ProjectPath.ImageFolder, directoryName);
public static listDirectory(req: Request, res: Response, next: NextFunction) {
let directoryName = req.params.directory || "/";
let absoluteDirectoryName = path.join(ProjectPath.ImageFolder, directoryName);
if (!fs.statSync(absoluteDirectoryName).isDirectory()) {
return next();
}
if (!fs.statSync(absoluteDirectoryName).isDirectory()) {
return next();
}
ObjectManagerRepository.getInstance().getGalleryManager().listDirectory(directoryName, (err, directory: DirectoryDTO) => {
if (err || !directory) {
Logger.warn(LOG_TAG, "Error during listing the directory", err);
console.error(err);
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
ObjectManagerRepository.getInstance().getGalleryManager().listDirectory(directoryName, (err, directory: DirectoryDTO) => {
if (err || !directory) {
Logger.warn(LOG_TAG, "Error during listing the directory", err);
console.error(err);
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
req.resultPipe = new ContentWrapper(directory, null);
req.resultPipe = new ContentWrapper(directory, null);
return next();
});
return next();
});
}
public static removeCyclicDirectoryReferences(req: Request, res: Response, next: NextFunction) {
if (!req.resultPipe)
return next();
let cw: ContentWrapper = req.resultPipe;
let removeDirs = (dir) => {
dir.photos.forEach((photo: PhotoDTO) => {
photo.directory = null;
});
dir.directories.forEach((directory: DirectoryDTO) => {
removeDirs(directory);
directory.parent = null;
});
};
if (cw.directory) {
removeDirs(cw.directory);
}
public static removeCyclicDirectoryReferences(req: Request, res: Response, next: NextFunction) {
if (!req.resultPipe)
return next();
let cw: ContentWrapper = req.resultPipe;
let removeDirs = (dir) => {
dir.photos.forEach((photo: PhotoDTO) => {
photo.directory = null;
});
dir.directories.forEach((directory: DirectoryDTO) => {
removeDirs(directory);
directory.parent = null;
});
};
if (cw.directory) {
removeDirs(cw.directory);
}
return next();
}
return next();
public static loadImage(req: Request, res: Response, next: NextFunction) {
if (!(req.params.imagePath)) {
return next();
}
let fullImagePath = path.join(ProjectPath.ImageFolder, req.params.imagePath);
if (fs.statSync(fullImagePath).isDirectory()) {
return next();
}
//check if thumbnail already exist
if (fs.existsSync(fullImagePath) === false) {
return next(new Error(ErrorCodes.GENERAL_ERROR, "no such file :" + fullImagePath));
}
req.resultPipe = fullImagePath;
return next();
}
public static search(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Search.searchEnabled === false) {
return next();
}
if (!(req.params.text)) {
return next();
}
let type: SearchTypes;
if (req.query.type) {
type = parseInt(req.query.type);
}
ObjectManagerRepository.getInstance().getSearchManager().search(req.params.text, type, (err, result: SearchResultDTO) => {
if (err || !result) {
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
req.resultPipe = new ContentWrapper(null, result);
return next();
});
}
public static instantSearch(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Search.instantSearchEnabled === false) {
return next();
}
if (!(req.params.text)) {
return next();
}
public static loadImage(req: Request, res: Response, next: NextFunction) {
if (!(req.params.imagePath)) {
return next();
}
ObjectManagerRepository.getInstance().getSearchManager().instantSearch(req.params.text, (err, result: SearchResultDTO) => {
if (err || !result) {
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
req.resultPipe = new ContentWrapper(null, result);
return next();
});
}
let fullImagePath = path.join(ProjectPath.ImageFolder, req.params.imagePath);
if (fs.statSync(fullImagePath).isDirectory()) {
return next();
}
//check if thumbnail already exist
if (fs.existsSync(fullImagePath) === false) {
return next(new Error(ErrorCodes.GENERAL_ERROR, "no such file :" + fullImagePath));
}
req.resultPipe = fullImagePath;
return next();
public static autocomplete(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Search.autocompleteEnabled === false) {
return next();
}
if (!(req.params.text)) {
return next();
}
public static search(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Search.searchEnabled === false) {
return next();
}
if (!(req.params.text)) {
return next();
}
let type: SearchTypes;
if (req.query.type) {
type = parseInt(req.query.type);
}
ObjectManagerRepository.getInstance().getSearchManager().search(req.params.text, type, (err, result: SearchResultDTO) => {
if (err || !result) {
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
req.resultPipe = new ContentWrapper(null, result);
return next();
});
}
ObjectManagerRepository.getInstance().getSearchManager().autocomplete(req.params.text, (err, items: Array<AutoCompleteItem>) => {
if (err || !items) {
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
req.resultPipe = items;
return next();
});
}
public static instantSearch(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Search.instantSearchEnabled === false) {
return next();
}
if (!(req.params.text)) {
return next();
}
ObjectManagerRepository.getInstance().getSearchManager().instantSearch(req.params.text, (err, result: SearchResultDTO) => {
if (err || !result) {
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
req.resultPipe = new ContentWrapper(null, result);
return next();
});
}
public static autocomplete(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Search.autocompleteEnabled === false) {
return next();
}
if (!(req.params.text)) {
return next();
}
ObjectManagerRepository.getInstance().getSearchManager().autocomplete(req.params.text, (err, items: Array<AutoCompleteItem>) => {
if (err || !items) {
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
req.resultPipe = items;
return next();
});
}
}
}

View File

@ -5,49 +5,49 @@ import {Message} from "../../common/entities/Message";
export class RenderingMWs {
public static renderResult(req:Request, res:Response, next:NextFunction) {
if (!req.resultPipe)
return next();
public static renderResult(req: Request, res: Response, next: NextFunction) {
if (!req.resultPipe)
return next();
return RenderingMWs.renderMessage(res, req.resultPipe);
return RenderingMWs.renderMessage(res, req.resultPipe);
}
public static renderSessionUser(req: Request, res: Response, next: NextFunction) {
if (!(req.session.user)) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
let user = Utils.clone(req.session.user);
delete user.password;
RenderingMWs.renderMessage(res, user);
}
public static renderSessionUser(req:Request, res:Response, next:NextFunction) {
if (!(req.session.user)) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
public static renderFile(req: Request, res: Response, next: NextFunction) {
if (!req.resultPipe)
return next();
let user = Utils.clone(req.session.user);
delete user.password;
RenderingMWs.renderMessage(res, user);
}
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) {
let message = new Message<string>(null, "ok");
res.json(message);
}
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);
}
return next(err);
return res.sendFile(req.resultPipe);
}
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 {
if (err instanceof Error) {
let message = new Message<any>(err, null);
return res.json(message);
}
return next(err);
}
protected static renderMessage<T>(res:Response, content:T) {
let message = new Message<T>(null, content);
res.json(message);
}
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,
})
.on('done', (out) => {
return next(out);
}).on('error', (error) => {
console.log(error);
return next(new Error(ErrorCodes.THUMBNAIL_GENERATION_ERROR, error));
});
};
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,228 +1,64 @@
///<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) {
Logger.silly(LOG_TAG, "scanning directory:", relativeDirectoryName);
let directoryName = path.basename(relativeDirectoryName);
let directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep);
let absoluteDirectoryName = path.join(ProjectPath.ImageFolder, relativeDirectoryName);
public static scanDirectory(relativeDirectoryName: string, cb: (error: any, result: DirectoryDTO) => void) {
Logger.silly(LOG_TAG, "scanning directory:", relativeDirectoryName);
let directoryName = path.basename(relativeDirectoryName);
let directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep);
let absoluteDirectoryName = path.join(ProjectPath.ImageFolder, relativeDirectoryName);
pool.send({
relativeDirectoryName,
directoryName,
directoryParent,
absoluteDirectoryName
}).on('done', (error: any, result: DirectoryDTO) => {
if (error || !result) {
return cb(error, result);
}
let input = <DiskManagerTask.PoolInput>{
relativeDirectoryName,
directoryName,
directoryParent,
absoluteDirectoryName
};
let addDirs = (dir: DirectoryDTO) => {
dir.photos.forEach((ph) => {
ph.directory = dir;
});
dir.directories.forEach((d) => {
addDirs(d);
});
};
addDirs(result);
return cb(error, result);
}).on('error', (error) => {
return cb(error, null);
let done = (error: any, result: DirectoryDTO) => {
if (error || !result) {
return cb(error, result);
}
let addDirs = (dir: DirectoryDTO) => {
dir.photos.forEach((ph) => {
ph.directory = dir;
});
dir.directories.forEach((d) => {
addDirs(d);
});
};
addDirs(result);
return cb(error, result);
};
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

@ -5,70 +5,70 @@ import {MySQLConnection} from "./mysql/MySQLConnection";
export class ObjectManagerRepository {
private _galleryManager: IGalleryManager;
private _userManager: IUserManager;
private _searchManager: ISearchManager;
private static _instance: ObjectManagerRepository = null;
private _galleryManager: IGalleryManager;
private _userManager: IUserManager;
private _searchManager: ISearchManager;
private static _instance: ObjectManagerRepository = null;
public static InitMemoryManagers() {
const GalleryManager = require("./memory/GalleryManager").GalleryManager;
const UserManager = require("./memory/UserManager").UserManager;
const SearchManager = require("./memory/SearchManager").SearchManager;
public static InitMemoryManagers() {
const GalleryManager = require("./memory/GalleryManager").GalleryManager;
const UserManager = require("./memory/UserManager").UserManager;
const SearchManager = require("./memory/SearchManager").SearchManager;
ObjectManagerRepository.getInstance().setGalleryManager(new GalleryManager());
ObjectManagerRepository.getInstance().setUserManager(new UserManager());
ObjectManagerRepository.getInstance().setSearchManager(new SearchManager());
}
public static InitMySQLManagers(): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
MySQLConnection.init().then(() => {
const GalleryManager = require("./mysql/GalleryManager").GalleryManager;
const UserManager = require("./mysql/UserManager").UserManager;
const SearchManager = require("./mysql/SearchManager").SearchManager;
ObjectManagerRepository.getInstance().setGalleryManager(new GalleryManager());
ObjectManagerRepository.getInstance().setUserManager(new UserManager());
ObjectManagerRepository.getInstance().setSearchManager(new SearchManager());
console.log("MySQL DB inited");
resolve(true);
}).catch(err => reject(err));
});
}
public static getInstance() {
if (this._instance === null) {
this._instance = new ObjectManagerRepository();
}
return this._instance;
}
public static InitMySQLManagers(): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
MySQLConnection.init().then(() => {
const GalleryManager = require("./mysql/GalleryManager").GalleryManager;
const UserManager = require("./mysql/UserManager").UserManager;
const SearchManager = require("./mysql/SearchManager").SearchManager;
ObjectManagerRepository.getInstance().setGalleryManager(new GalleryManager());
ObjectManagerRepository.getInstance().setUserManager(new UserManager());
ObjectManagerRepository.getInstance().setSearchManager(new SearchManager());
console.log("MySQL DB inited");
resolve(true);
}).catch(err => reject(err));
});
}
public static getInstance() {
if (this._instance === null) {
this._instance = new ObjectManagerRepository();
}
return this._instance;
}
public static reset() {
this._instance = null;
}
public static reset() {
this._instance = null;
}
getGalleryManager(): IGalleryManager {
return this._galleryManager;
}
getGalleryManager(): IGalleryManager {
return this._galleryManager;
}
setGalleryManager(value: IGalleryManager) {
this._galleryManager = value;
}
setGalleryManager(value: IGalleryManager) {
this._galleryManager = value;
}
getUserManager(): IUserManager {
return this._userManager;
}
getUserManager(): IUserManager {
return this._userManager;
}
setUserManager(value: IUserManager) {
this._userManager = value;
}
setUserManager(value: IUserManager) {
this._userManager = value;
}
getSearchManager(): ISearchManager {
return this._searchManager;
}
getSearchManager(): ISearchManager {
return this._searchManager;
}
setSearchManager(value: ISearchManager) {
this._searchManager = value;
}
setSearchManager(value: ISearchManager) {
this._searchManager = value;
}
}
}

View File

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

View File

@ -2,27 +2,27 @@ import {AuthenticationMWs} from "../middlewares/user/AuthenticationMWs";
import {UserRoles} from "../../common/entities/UserDTO";
export class AdminRouter {
public static route(app: any) {
public static route(app: any) {
this.addResetDB(app);
this.addIndexGallery(app);
}
this.addResetDB(app);
this.addIndexGallery(app);
}
private static addResetDB(app) {
app.post("/api/admin/db/reset",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin)
//TODO: implement
);
};
private static addResetDB(app) {
app.post("/api/admin/db/reset",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin)
//TODO: implement
);
};
private static addIndexGallery(app) {
app.post("/api/admin/gallery/index",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin)
//TODO: implement
);
};
private static addIndexGallery(app) {
app.post("/api/admin/gallery/index",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin)
//TODO: implement
);
};
}
}

View File

@ -5,27 +5,27 @@ import Request = Express.Request;
import Response = Express.Response;
export class ErrorRouter {
public static route(app: any) {
public static route(app: any) {
this.addApiErrorHandler(app);
this.addGenericHandler(app);
}
this.addApiErrorHandler(app);
this.addGenericHandler(app);
}
private static addApiErrorHandler(app) {
app.use("/api/*",
RenderingMWs.renderError
);
};
private static addApiErrorHandler(app) {
app.use("/api/*",
RenderingMWs.renderError
);
};
private static addGenericHandler(app) {
app.use((err: any, req: Request, res: Response, next: Function) => {
private static addGenericHandler(app) {
app.use((err: any, req: Request, res: Response, next: Function) => {
//Flush out the stack to the console
Logger.error(err);
next(new Error(ErrorCodes.SERVER_ERROR, "Unknown server side error"));
},
RenderingMWs.renderError
);
}
//Flush out the stack to the console
Logger.error(err);
next(new Error(ErrorCodes.SERVER_ERROR, "Unknown server side error"));
},
RenderingMWs.renderError
);
}
}
}

View File

@ -4,82 +4,82 @@ import {RenderingMWs} from "../middlewares/RenderingMWs";
import {ThumbnailGeneratorMWs} from "../middlewares/thumbnail/ThumbnailGeneratorMWs";
export class GalleryRouter {
public static route(app: any) {
public static route(app: any) {
this.addGetImageIcon(app);
this.addGetImageThumbnail(app);
this.addGetImage(app);
this.addDirectoryList(app);
this.addGetImageIcon(app);
this.addGetImageThumbnail(app);
this.addGetImage(app);
this.addDirectoryList(app);
this.addSearch(app);
this.addInstantSearch(app);
this.addAutoComplete(app);
}
this.addSearch(app);
this.addInstantSearch(app);
this.addAutoComplete(app);
}
private static addDirectoryList(app) {
app.get(["/api/gallery/content/:directory(*)", "/api/gallery/", "/api/gallery//"],
AuthenticationMWs.authenticate,
GalleryMWs.listDirectory,
ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences,
RenderingMWs.renderResult
);
};
private static addDirectoryList(app) {
app.get(["/api/gallery/content/:directory(*)", "/api/gallery/", "/api/gallery//"],
AuthenticationMWs.authenticate,
GalleryMWs.listDirectory,
ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences,
RenderingMWs.renderResult
);
};
private static addGetImage(app) {
app.get(["/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))"],
AuthenticationMWs.authenticate,
GalleryMWs.loadImage,
RenderingMWs.renderFile
);
};
private static addGetImage(app) {
app.get(["/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))"],
AuthenticationMWs.authenticate,
GalleryMWs.loadImage,
RenderingMWs.renderFile
);
};
private static addGetImageThumbnail(app) {
app.get("/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/thumbnail/:size?",
AuthenticationMWs.authenticate,
GalleryMWs.loadImage,
ThumbnailGeneratorMWs.generateThumbnail,
RenderingMWs.renderFile
);
};
private static addGetImageThumbnail(app) {
app.get("/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/thumbnail/:size?",
AuthenticationMWs.authenticate,
GalleryMWs.loadImage,
ThumbnailGeneratorMWs.generateThumbnail,
RenderingMWs.renderFile
);
};
private static addGetImageIcon(app) {
app.get("/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/icon",
AuthenticationMWs.authenticate,
GalleryMWs.loadImage,
ThumbnailGeneratorMWs.generateIcon,
RenderingMWs.renderFile
);
};
private static addGetImageIcon(app) {
app.get("/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/icon",
AuthenticationMWs.authenticate,
GalleryMWs.loadImage,
ThumbnailGeneratorMWs.generateIcon,
RenderingMWs.renderFile
);
};
private static addSearch(app) {
app.get("/api/search/:text",
AuthenticationMWs.authenticate,
GalleryMWs.search,
ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences,
RenderingMWs.renderResult
);
};
private static addSearch(app) {
app.get("/api/search/:text",
AuthenticationMWs.authenticate,
GalleryMWs.search,
ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences,
RenderingMWs.renderResult
);
};
private static addInstantSearch(app) {
app.get("/api/instant-search/:text",
AuthenticationMWs.authenticate,
GalleryMWs.instantSearch,
ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences,
RenderingMWs.renderResult
);
};
private static addInstantSearch(app) {
app.get("/api/instant-search/:text",
AuthenticationMWs.authenticate,
GalleryMWs.instantSearch,
ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences,
RenderingMWs.renderResult
);
};
private static addAutoComplete(app) {
app.get("/api/autocomplete/:text",
AuthenticationMWs.authenticate,
GalleryMWs.autocomplete,
RenderingMWs.renderResult
);
};
private static addAutoComplete(app) {
app.get("/api/autocomplete/:text",
AuthenticationMWs.authenticate,
GalleryMWs.autocomplete,
RenderingMWs.renderResult
);
};
}
}

View File

@ -6,32 +6,37 @@ import {Config} from "../../common/config/private/Config";
export class PublicRouter {
public static route(app) {
app.use((req: Request, res: Response, next: NextFunction) => {
res.tpl = {};
public static route(app) {
app.use((req: Request, res: Response, next: NextFunction) => {
res.tpl = {};
res.tpl.user = null;
if (req.session.user) {
let user = Utils.clone(req.session.user);
delete user.password;
res.tpl.user = user;
}
res.tpl.clientConfig = Config.Client;
res.tpl.user = null;
if (req.session.user) {
let user = Utils.clone(req.session.user);
delete user.password;
res.tpl.user = user;
}
res.tpl.clientConfig = Config.Client;
return next();
});
return next();
});
app.use(_express.static(_path.resolve(__dirname, './../../frontend')));
app.use('/node_modules', _express.static(_path.resolve(__dirname, './../../node_modules')));
app.use('/common', _express.static(_path.resolve(__dirname, './../../common')));
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'));
});
const renderIndex = (req: Request, res: Response) => {
res.render(_path.resolve(__dirname, './../../frontend/index.ejs'), res.tpl);
};
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')));
app.get(['/', '/login', "/gallery*", "/admin", "/search*"], renderIndex);
const renderIndex = (req: Request, res: Response) => {
res.render(_path.resolve(__dirname, './../../dist/index.html'));
};
}
}
}
}

View File

@ -2,27 +2,27 @@ import {AuthenticationMWs} from "../middlewares/user/AuthenticationMWs";
import {UserRoles} from "../../common/entities/UserDTO";
export class SharingRouter {
public static route(app: any) {
public static route(app: any) {
this.addGetSharing(app);
this.addUpdateSharing(app);
}
this.addGetSharing(app);
this.addUpdateSharing(app);
}
private static addGetSharing(app) {
app.get("/api/share/:directory",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.User)
//TODO: implement
);
};
private static addGetSharing(app) {
app.get("/api/share/:directory",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.User)
//TODO: implement
);
};
private static addUpdateSharing(app) {
app.post("/api/share/:directory",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.User)
//TODO: implement
);
};
private static addUpdateSharing(app) {
app.post("/api/share/:directory",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.User)
//TODO: implement
);
};
}
}

View File

@ -5,92 +5,92 @@ import {UserRequestConstrainsMWs} from "../middlewares/user/UserRequestConstrain
import {RenderingMWs} from "../middlewares/RenderingMWs";
export class UserRouter {
public static route(app) {
this.addLogin(app);
this.addLogout(app);
this.addGetSessionUser(app);
this.addChangePassword(app);
public static route(app) {
this.addLogin(app);
this.addLogout(app);
this.addGetSessionUser(app);
this.addChangePassword(app);
this.addCreateUser(app);
this.addDeleteUser(app);
this.addListUsers(app);
this.addChangeRole(app);
}
this.addCreateUser(app);
this.addDeleteUser(app);
this.addListUsers(app);
this.addChangeRole(app);
}
private static addLogin(app) {
app.post("/api/user/login",
AuthenticationMWs.inverseAuthenticate,
AuthenticationMWs.login,
RenderingMWs.renderSessionUser
);
};
private static addLogin(app) {
app.post("/api/user/login",
AuthenticationMWs.inverseAuthenticate,
AuthenticationMWs.login,
RenderingMWs.renderSessionUser
);
};
private static addLogout(app) {
app.post("/api/user/logout",
AuthenticationMWs.authenticate,
AuthenticationMWs.logout,
RenderingMWs.renderOK
);
};
private static addLogout(app) {
app.post("/api/user/logout",
AuthenticationMWs.authenticate,
AuthenticationMWs.logout,
RenderingMWs.renderOK
);
};
private static addGetSessionUser(app) {
app.get("/api/user/login",
AuthenticationMWs.authenticate,
RenderingMWs.renderSessionUser
);
};
private static addGetSessionUser(app) {
app.get("/api/user/login",
AuthenticationMWs.authenticate,
RenderingMWs.renderSessionUser
);
};
private static addChangePassword(app) {
app.post("/api/user/:id/password",
AuthenticationMWs.authenticate,
UserRequestConstrainsMWs.forceSelfRequest,
UserMWs.changePassword,
RenderingMWs.renderOK
);
};
private static addChangePassword(app) {
app.post("/api/user/:id/password",
AuthenticationMWs.authenticate,
UserRequestConstrainsMWs.forceSelfRequest,
UserMWs.changePassword,
RenderingMWs.renderOK
);
};
private static addCreateUser(app) {
app.put("/api/user",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
UserMWs.createUser,
RenderingMWs.renderOK
);
};
private static addCreateUser(app) {
app.put("/api/user",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
UserMWs.createUser,
RenderingMWs.renderOK
);
};
private static addDeleteUser(app) {
app.delete("/api/user/:id",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
UserRequestConstrainsMWs.notSelfRequest,
UserMWs.deleteUser,
RenderingMWs.renderOK
);
};
private static addDeleteUser(app) {
app.delete("/api/user/:id",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
UserRequestConstrainsMWs.notSelfRequest,
UserMWs.deleteUser,
RenderingMWs.renderOK
);
};
private static addListUsers(app) {
app.get("/api/user/list",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
UserMWs.listUsers,
RenderingMWs.renderResult
);
};
private static addListUsers(app) {
app.get("/api/user/list",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
UserMWs.listUsers,
RenderingMWs.renderResult
);
};
private static addChangeRole(app) {
app.post("/api/user/:id/role",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
UserRequestConstrainsMWs.notSelfRequestOr2Admins,
UserMWs.changeRole,
RenderingMWs.renderOK
);
};
private static addChangeRole(app) {
app.post("/api/user/:id/role",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Admin),
UserRequestConstrainsMWs.notSelfRequestOr2Admins,
UserMWs.changeRole,
RenderingMWs.renderOK
);
};
}
}

View File

@ -18,170 +18,170 @@ import {DatabaseType} from "../common/config/private/IPrivateConfig";
const LOG_TAG = "[server]";
export class Server {
private debug: any;
private app: any;
private server: any;
private debug: any;
private app: any;
private server: any;
constructor() {
this.init();
}
constructor() {
this.init();
}
async init() {
Logger.info(LOG_TAG, "config:");
Logger.info(LOG_TAG, JSON.stringify(Config, null, '\t'));
async init() {
Logger.info(LOG_TAG, "config:");
Logger.info(LOG_TAG, JSON.stringify(Config, null, '\t'));
this.app = _express();
this.app = _express();
this.app.use(expressWinston.logger({
transports: [
new winston.transports.Console({
level: 'silly',
json: false,
colorize: true,
timestamp: function () {
return (new Date()).toLocaleString();
},
formatter: (options) => {
// Return string will be passed to logger.
return options.timestamp() + '[' + winston['config']['colorize'](options.level, options.level.toUpperCase()) + '] ' +
(undefined !== options.message ? options.message : '') +
(options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : '' );
},
debugStdout: true
})
],
meta: false, // optional: control whether you want to log the meta data about the request (default to true)
msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}"
expressFormat: true, // Use the default Express/morgan request formatting. Enabling this will override any msg if true. Will only output colors with colorize set to true
colorize: true, // Color the text and status code, using the Express/morgan color palette (text: gray, status: default green, 3XX cyan, 4XX yellow, 5XX red).
level: (req) => {
if (req.url.indexOf("/api/") !== -1) {
return "verbose";
}
return req.url.indexOf("node_modules") !== -1 ? "silly" : "debug"
}
}));
this.app.set('view engine', 'ejs');
/**
* Session above all
*/
this.app.use(_session({
name: "pigallery2-session",
secret: 'PiGallery2 secret',
cookie: {
maxAge: 60000 * 10,
httpOnly: false
},
resave: true,
saveUninitialized: false
}));
/**
* Parse parameters in POST
*/
// for parsing application/json
this.app.use(_bodyParser.json());
if (Config.Server.database.type == DatabaseType.mysql) {
try {
await ObjectManagerRepository.InitMySQLManagers();
} catch (err) {
Logger.warn(LOG_TAG, "[MYSQL error]", err);
Logger.warn(LOG_TAG, "Error during initializing mysql falling back to memory DB");
Config.setDatabaseType(DatabaseType.memory);
await ObjectManagerRepository.InitMemoryManagers();
}
} else {
await ObjectManagerRepository.InitMemoryManagers();
this.app.use(expressWinston.logger({
transports: [
new winston.transports.Console({
level: 'silly',
json: false,
colorize: true,
timestamp: function () {
return (new Date()).toLocaleString();
},
formatter: (options) => {
// Return string will be passed to logger.
return options.timestamp() + '[' + winston['config']['colorize'](options.level, options.level.toUpperCase()) + '] ' +
(undefined !== options.message ? options.message : '') +
(options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : '' );
},
debugStdout: true
})
],
meta: false, // optional: control whether you want to log the meta data about the request (default to true)
msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}"
expressFormat: true, // Use the default Express/morgan request formatting. Enabling this will override any msg if true. Will only output colors with colorize set to true
colorize: true, // Color the text and status code, using the Express/morgan color palette (text: gray, status: default green, 3XX cyan, 4XX yellow, 5XX red).
level: (req) => {
if (req.url.indexOf("/api/") !== -1) {
return "verbose";
}
return req.url.indexOf("node_modules") !== -1 ? "silly" : "debug"
}
}));
if (Config.Server.thumbnail.hardwareAcceleration == true) {
try {
const sharp = require("sharp");
sharp();
} catch (err) {
Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] sharp module error: ", err);
Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." +
" 'Sharp' node module is not found." +
" Falling back to JS based thumbnail generation");
Config.Server.thumbnail.hardwareAcceleration = false;
}
}
PublicRouter.route(this.app);
UserRouter.route(this.app);
GalleryRouter.route(this.app);
SharingRouter.route(this.app);
AdminRouter.route(this.app);
ErrorRouter.route(this.app);
// Get PORT from environment and store in Express.
this.app.set('port', Config.Server.port);
// Create HTTP server.
this.server = _http.createServer(this.app);
//Listen on provided PORT, on all network interfaces.
this.server.listen(Config.Server.port);
this.server.on('error', this.onError);
this.server.on('listening', this.onListening);
}
this.app.set('view engine', 'ejs');
/**
* Event listener for HTTP server "error" event.
* Session above all
*/
private onError = (error: any) => {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof Config.Server.port === 'string'
? 'Pipe ' + Config.Server.port
: 'Port ' + Config.Server.port;
// handle specific listen error with friendly messages
switch (error.code) {
case 'EACCES':
Logger.error(LOG_TAG, bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
Logger.error(LOG_TAG, bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
};
this.app.use(_session({
name: "pigallery2-session",
secret: 'PiGallery2 secret',
cookie: {
maxAge: 60000 * 10,
httpOnly: false
},
resave: true,
saveUninitialized: false
}));
/**
* Event listener for HTTP server "listening" event.
* Parse parameters in POST
*/
private onListening = () => {
let addr = this.server.address();
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
Logger.info(LOG_TAG, 'Listening on ' + bind);
};
// for parsing application/json
this.app.use(_bodyParser.json());
if (Config.Server.database.type == DatabaseType.mysql) {
try {
await ObjectManagerRepository.InitMySQLManagers();
} catch (err) {
Logger.warn(LOG_TAG, "[MYSQL error]", err);
Logger.warn(LOG_TAG, "Error during initializing mysql falling back to memory DB");
Config.setDatabaseType(DatabaseType.memory);
await ObjectManagerRepository.InitMemoryManagers();
}
} else {
await ObjectManagerRepository.InitMemoryManagers();
}
if (Config.Server.thumbnail.hardwareAcceleration == true) {
try {
const sharp = require("sharp");
sharp();
} catch (err) {
Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] sharp module error: ", err);
Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." +
" 'Sharp' node module is not found." +
" Falling back to JS based thumbnail generation");
Config.Server.thumbnail.hardwareAcceleration = false;
}
}
PublicRouter.route(this.app);
UserRouter.route(this.app);
GalleryRouter.route(this.app);
SharingRouter.route(this.app);
AdminRouter.route(this.app);
ErrorRouter.route(this.app);
// Get PORT from environment and store in Express.
this.app.set('port', Config.Server.port);
// Create HTTP server.
this.server = _http.createServer(this.app);
//Listen on provided PORT, on all network interfaces.
this.server.listen(Config.Server.port);
this.server.on('error', this.onError);
this.server.on('listening', this.onListening);
}
/**
* Event listener for HTTP server "error" event.
*/
private onError = (error: any) => {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof Config.Server.port === 'string'
? 'Pipe ' + Config.Server.port
: 'Port ' + Config.Server.port;
// handle specific listen error with friendly messages
switch (error.code) {
case 'EACCES':
Logger.error(LOG_TAG, bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
Logger.error(LOG_TAG, bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
};
/**
* Event listener for HTTP server "listening" event.
*/
private onListening = () => {
let addr = this.server.address();
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
Logger.info(LOG_TAG, 'Listening on ' + bind);
};
}
if (process.env.DEBUG) {
Logger.debug(LOG_TAG, "Running in DEBUG mode");
Logger.debug(LOG_TAG, "Running in DEBUG mode");
}
new Server();
new Server();

View File

@ -1,14 +1,14 @@
export var MessageTypes = {
Client: {
Login: {
Authenticate: "Authenticate"
}
},
Server: {
Login: {
Authenticated: "Authenticated"
}
Client: {
Login: {
Authenticate: "Authenticate"
}
};
},
Server: {
Login: {
Authenticated: "Authenticated"
}
}
};

View File

@ -1,107 +1,107 @@
export class Utils {
static clone<T>(object: T): T {
return JSON.parse(JSON.stringify(object));
static clone<T>(object: T): T {
return JSON.parse(JSON.stringify(object));
}
static equalsFilter(object: any, filter: any): boolean {
let keys = Object.keys(filter);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (object[key] !== filter[key]) {
return false;
}
}
static equalsFilter(object: any, filter: any): boolean {
return true;
}
let keys = Object.keys(filter);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (object[key] !== filter[key]) {
return false;
}
static concatUrls(...args: Array<string>) {
let url = "";
for (let i = 0; i < args.length; i++) {
if (args[i] === "" || typeof args[i] === "undefined") continue;
let part = args[i].replace("\\", "/");
if (part === "/" || part === "./") continue;
url += part + "/";
}
url = url.replace("//", "/");
return url.substring(0, url.length - 1);
}
public static updateKeys(targetObject: any, sourceObject: any) {
Object.keys(sourceObject).forEach((key) => {
if (typeof targetObject[key] === "undefined") {
return;
}
if (typeof targetObject[key] === "object") {
Utils.updateKeys(targetObject[key], sourceObject[key]);
} else {
targetObject[key] = sourceObject[key];
}
});
}
public static setKeys(targetObject: any, sourceObject: any) {
Object.keys(sourceObject).forEach((key) => {
if (typeof targetObject[key] === "object") {
Utils.setKeys(targetObject[key], sourceObject[key]);
} else {
targetObject[key] = sourceObject[key];
}
});
}
public static setKeysForced(targetObject: any, sourceObject: any) {
Object.keys(sourceObject).forEach((key) => {
if (typeof sourceObject[key] === "object") {
if (typeof targetObject[key] === "undefined") {
targetObject[key] = {};
}
Utils.setKeysForced(targetObject[key], sourceObject[key]);
} else {
targetObject[key] = sourceObject[key];
}
});
}
return true;
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;
}
let key = parseInt(enumMember, 10);
if (key >= 0) {
arr.push({key: key, value: EnumType[enumMember]});
}
}
return arr;
}
static concatUrls(...args: Array<string>) {
let url = "";
for (let i = 0; i < args.length; i++) {
if (args[i] === "" || typeof args[i] === "undefined") continue;
public static findClosest(number: number, arr: Array<number>) {
let part = args[i].replace("\\", "/");
if (part === "/" || part === "./") continue;
let curr = arr[0];
let diff = Math.abs(number - curr);
url += part + "/";
}
url = url.replace("//", "/");
arr.forEach((value) => {
return url.substring(0, url.length - 1);
}
let newDiff = Math.abs(number - value);
public static updateKeys(targetObject: any, sourceObject: any) {
Object.keys(sourceObject).forEach((key) => {
if (typeof targetObject[key] === "undefined") {
return;
}
if (typeof targetObject[key] === "object") {
Utils.updateKeys(targetObject[key], sourceObject[key]);
} else {
targetObject[key] = sourceObject[key];
}
});
}
if (newDiff < diff) {
diff = newDiff;
curr = value;
}
public static setKeys(targetObject: any, sourceObject: any) {
Object.keys(sourceObject).forEach((key) => {
if (typeof targetObject[key] === "object") {
Utils.setKeys(targetObject[key], sourceObject[key]);
} else {
targetObject[key] = sourceObject[key];
}
});
}
});
public static setKeysForced(targetObject: any, sourceObject: any) {
Object.keys(sourceObject).forEach((key) => {
if (typeof sourceObject[key] === "object") {
if (typeof targetObject[key] === "undefined") {
targetObject[key] = {};
}
Utils.setKeysForced(targetObject[key], sourceObject[key]);
} else {
targetObject[key] = sourceObject[key];
}
});
}
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;
}
let key = parseInt(enumMember, 10);
if (key >= 0) {
arr.push({key: key, value: EnumType[enumMember]});
}
}
return arr;
}
public static findClosest(number: number, arr: Array<number>) {
let curr = arr[0];
let diff = Math.abs(number - curr);
arr.forEach((value) => {
let newDiff = Math.abs(number - value);
if (newDiff < diff) {
diff = newDiff;
curr = value;
}
});
return curr;
}
return curr;
}
}

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

@ -1,16 +1,16 @@
export enum SearchTypes {
directory = 1,
keyword = 2,
position = 3,
image = 4
directory = 1,
keyword = 2,
position = 3,
image = 4
}
export class AutoCompleteItem {
constructor(public text:string, public type:SearchTypes) {
}
constructor(public text: string, public type: SearchTypes) {
}
equals(other:AutoCompleteItem) {
return this.text === other.text && this.type === other.type;
}
equals(other: AutoCompleteItem) {
return this.text === other.text && this.type === other.type;
}
}

View File

@ -2,12 +2,12 @@ import {DirectoryDTO} from "./DirectoryDTO";
import {SearchResultDTO} from "./SearchResult";
export class ContentWrapper {
public directory: DirectoryDTO;
public searchResult: SearchResultDTO;
public directory: DirectoryDTO;
public searchResult: SearchResultDTO;
constructor(directory: DirectoryDTO = null, searchResult: SearchResultDTO = null) {
this.directory = directory;
this.searchResult = searchResult;
}
constructor(directory: DirectoryDTO = null, searchResult: SearchResultDTO = null) {
this.directory = directory;
this.searchResult = searchResult;
}
}
}

View File

@ -1,11 +1,11 @@
import {PhotoDTO} from "./PhotoDTO";
export interface DirectoryDTO {
id: number;
name: string;
path: string;
lastUpdate: number;
parent: DirectoryDTO;
directories: Array<DirectoryDTO>;
photos: Array<PhotoDTO>;
}
id: number;
name: string;
path: string;
lastUpdate: number;
parent: DirectoryDTO;
directories: Array<DirectoryDTO>;
photos: Array<PhotoDTO>;
}

View File

@ -1,22 +1,22 @@
export enum ErrorCodes{
NOT_AUTHENTICATED = 0,
ALREADY_AUTHENTICATED = 1,
NOT_AUTHORISED = 2,
CREDENTIAL_NOT_FOUND = 3,
NOT_AUTHENTICATED = 0,
ALREADY_AUTHENTICATED = 1,
NOT_AUTHORISED = 2,
CREDENTIAL_NOT_FOUND = 3,
USER_CREATION_ERROR = 4,
USER_CREATION_ERROR = 4,
GENERAL_ERROR = 5,
THUMBNAIL_GENERATION_ERROR = 6,
SERVER_ERROR = 7,
GENERAL_ERROR = 5,
THUMBNAIL_GENERATION_ERROR = 6,
SERVER_ERROR = 7,
USER_MANAGEMENT_DISABLED = 8
USER_MANAGEMENT_DISABLED = 8
}
export class Error {
constructor(public code: ErrorCodes, public message?: string) {
}
}
constructor(public code: ErrorCodes, public message?: string) {
}
}

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,11 +1,11 @@
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) {
this.error = error;
this.result = result;
}
}
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) {
super(id);
}
}
constructor(id: number, public oldPassword: string, public newPassword: string) {
super(id);
}
}

View File

@ -1,47 +1,47 @@
import {DirectoryDTO} from "./DirectoryDTO";
export interface PhotoDTO {
id: number;
name: string;
directory: DirectoryDTO;
metadata: PhotoMetadata;
readyThumbnails: Array<number>;
readyIcon: boolean;
id: number;
name: string;
directory: DirectoryDTO;
metadata: PhotoMetadata;
readyThumbnails: Array<number>;
readyIcon: boolean;
}
export interface PhotoMetadata {
keywords: Array<string>;
cameraData: CameraMetadata;
positionData: PositionMetaData;
size: ImageSize;
creationDate: number;
keywords: Array<string>;
cameraData: CameraMetadata;
positionData: PositionMetaData;
size: ImageSize;
creationDate: number;
}
export interface ImageSize {
width: number;
height: number;
width: number;
height: number;
}
export interface CameraMetadata {
ISO?: number;
model?: string;
maker?: string;
fStop?: number;
exposure?: number;
focalLength?: number;
lens?: string;
ISO?: number;
model?: string;
maker?: string;
fStop?: number;
exposure?: number;
focalLength?: number;
lens?: string;
}
export interface PositionMetaData {
GPSData?: GPSMetadata;
country?: string;
state?: string;
city?: string;
GPSData?: GPSMetadata;
country?: string;
state?: string;
city?: string;
}
export interface GPSMetadata {
latitude?: number;
longitude?: number;
altitude?: string;
latitude?: number;
longitude?: number;
altitude?: string;
}
}

View File

@ -2,8 +2,8 @@ import {DirectoryDTO} from "./DirectoryDTO";
import {PhotoDTO} from "./PhotoDTO";
import {SearchTypes} from "./AutoCompleteItem";
export interface SearchResultDTO {
searchText: string;
searchType: SearchTypes;
directories: Array<DirectoryDTO>;
photos: Array<PhotoDTO>;
}
searchText: string;
searchType: SearchTypes;
directories: Array<DirectoryDTO>;
photos: Array<PhotoDTO>;
}

View File

@ -1,14 +1,14 @@
export enum UserRoles{
Guest = 1,
User = 2,
Admin = 3,
Developer = 4,
Guest = 1,
User = 2,
Admin = 3,
Developer = 4,
}
export interface UserDTO {
id: number;
name: string;
password: string;
role: UserRoles;
}
id: number;
name: string;
password: string;
role: UserRoles;
}

View File

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

View File

@ -1,30 +1,30 @@
function isFunction(functionToCheck: any) {
let getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
let getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}
export class Event<T> {
private handlers: {(data?: T): void;}[] = [];
private handlers: { (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 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}) {
this.handlers = this.handlers.filter(h => h !== handler);
}
public off(handler: { (data?: T): void }) {
this.handlers = this.handlers.filter(h => h !== handler);
}
public allOff() {
this.handlers = [];
}
public allOff() {
this.handlers = [];
}
public trigger(data?: T) {
if (this.handlers) {
this.handlers.slice(0).forEach(h => h(data));
}
public trigger(data?: T) {
if (this.handlers) {
this.handlers.slice(0).forEach(h => h(data));
}
}
}

View File

@ -1,21 +1,21 @@
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 }) {
this.handlers.push(handler);
}
public on(handler: { (data?: T, data2?: M): void }) {
this.handlers.push(handler);
}
public off(handler: { (data?: T,data2?: M): void }) {
this.handlers = this.handlers.filter(h => h !== handler);
}
public off(handler: { (data?: T, data2?: M): void }) {
this.handlers = this.handlers.filter(h => h !== handler);
}
public allOff() {
this.handlers = [];
}
public allOff() {
this.handlers = [];
}
public trigger(data?: T,data2?: M) {
if (this.handlers) {
this.handlers.slice(0).forEach(h => h(data,data2));
}
public trigger(data?: T, data2?: M) {
if (this.handlers) {
this.handlers.slice(0).forEach(h => h(data, data2));
}
}
}

View File

@ -1,70 +1,74 @@
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 }) {
this.handlers.push(new EventLimitHandler(limit, handler));
if (this.lastTriggerValue != null) {
this.trigger(this.lastTriggerValue);
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 }) {
this.handlers.push(new SingleFireEventLimitHandler(limit, handler));
if (this.lastTriggerValue != null) {
this.trigger(this.lastTriggerValue);
}
}
public off(limit: T, handler: { (data?: T): void }) {
this.handlers = this.handlers.filter(h => h.handler !== handler && h.limit !== limit);
}
public allOff() {
this.handlers = [];
}
public trigger = (data?: T) => {
if (this.handlers) {
this.handlers.slice(0).forEach(h => {
if (h.limit <= data && (h.lastTriggerValue < h.limit || h.lastTriggerValue == null)) {
h.fire(data);
}
h.lastTriggerValue = data;
});
this.handlers = this.handlers.filter(h => h.isValid());
}
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 }) {
this.handlers = this.handlers.filter(h => h.handler !== handler && h.limit !== limit);
}
public allOff() {
this.handlers = [];
}
public trigger = (data?:T) => {
if (this.handlers) {
this.handlers.slice(0).forEach(h => {
if (h.limit <= data && (h.lastTriggerValue < h.limit || h.lastTriggerValue == null)) {
h.fire(data);
}
h.lastTriggerValue = data;
});
this.handlers = this.handlers.filter(h => h.isValid());
}
this.lastTriggerValue = data;
};
this.lastTriggerValue = data;
};
}
class EventLimitHandler<T> {
public lastTriggerValue:T = null;
constructor(public limit:T, public handler:{ (data?:T): void }) {
}
public fire(data?: T){
this.handler(data);
}
public isValid():boolean{
return true;
}
}
class SingleFireEventLimitHandler<T> extends EventLimitHandler<T>{
public fired = false;
constructor(public limit:T, public handler:{ (data?:T): void }) {
super(limit,handler);
}
public lastTriggerValue: T = null;
public fire(data?: T){
if(this.fired == false) {
this.handler(data);
}
this.fired = true
}
constructor(public limit: T, public handler: { (data?: T): void }) {
}
public isValid():boolean{
return this.fired === false;
}
public fire(data?: T) {
this.handler(data);
}
public isValid(): boolean {
return true;
}
}
class SingleFireEventLimitHandler<T> extends EventLimitHandler<T> {
public fired = false;
constructor(public limit: T, public handler: { (data?: T): void }) {
super(limit, handler);
}
public fire(data?: T) {
if (this.fired == false) {
this.handler(data);
}
this.fired = true
}
public isValid(): boolean {
return this.fired === false;
}
}

View File

@ -4,23 +4,23 @@ import {Router} from "@angular/router";
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']
selector: 'admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.css']
})
export class AdminComponent implements OnInit {
userManagementEnable: boolean = false;
userManagementEnable: boolean = false;
constructor(private _authService: AuthenticationService, private _router: Router) {
this.userManagementEnable = Config.Client.authenticationRequired;
}
constructor(private _authService: AuthenticationService, private _router: Router) {
this.userManagementEnable = Config.Client.authenticationRequired;
}
ngOnInit() {
if (!this._authService.isAuthenticated() || this._authService.getUser().role < UserRoles.Admin) {
this._router.navigate(['login']);
return;
}
ngOnInit() {
if (!this._authService.isAuthenticated() || this._authService.getUser().role < UserRoles.Admin) {
this._router.navigate(['login']);
return;
}
}
}

View File

@ -4,31 +4,31 @@ import {UserDTO} from "../../common/entities/UserDTO";
import {Router} from "@angular/router";
@Component({
selector: 'pi-gallery2-app',
template: `<router-outlet></router-outlet>`,
selector: 'pi-gallery2-app',
template: `<router-outlet></router-outlet>`,
})
export class AppComponent implements OnInit {
constructor(private _router: Router, private _authenticationService: AuthenticationService) {
}
constructor(private _router: Router, private _authenticationService: AuthenticationService) {
}
ngOnInit() {
this._authenticationService.OnUserChanged.on((user: UserDTO) => {
if (user != null) {
if (this._router.isActive('login', true)) {
console.log("routing");
this._router.navigate(["gallery", ""]);
}
} else {
if (this._router.isActive('login', true)) {
console.log("routing");
this._router.navigate(["login"]);
}
}
ngOnInit() {
this._authenticationService.OnUserChanged.on((user: UserDTO) => {
if (user != null) {
if (this._router.isActive('login', true)) {
console.log("routing");
this._router.navigate(["gallery", ""]);
}
} else {
if (this._router.isActive('login', true)) {
console.log("routing");
this._router.navigate(["login"]);
}
}
});
});
}
}
}
}

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,48 +31,58 @@ 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: [
BrowserModule,
FormsModule,
HttpModule,
appRoutes,
AgmCoreModule.forRoot({
apiKey: Config.Client.googleApiKey
})
],
declarations: [AppComponent,
LoginComponent,
AdminComponent,
GalleryComponent,
FrameComponent,
UserMangerSettingsComponent,
GalleryLightboxPhotoComponent,
GalleryPhotoLoadingComponent,
GalleryGridComponent,
GalleryDirectoryComponent,
GalleryLightboxComponent,
GalleryMapComponent,
GalleryMapLightboxComponent,
FrameComponent,
GallerySearchComponent,
GalleryNavigatorComponent,
GalleryPhotoComponent,
FrameComponent,
StringifyRole],
providers: [
NetworkService,
UserService,
GalleryCacheService,
GalleryService,
AuthenticationService,
ThumbnailLoaderService,
ThumbnailManagerService,
FullScreenService,
OverlayService],
imports: [
BrowserModule,
FormsModule,
HttpModule,
appRoutes,
AgmCoreModule.forRoot()
],
declarations: [AppComponent,
LoginComponent,
AdminComponent,
GalleryComponent,
FrameComponent,
UserMangerSettingsComponent,
GalleryLightboxPhotoComponent,
GalleryPhotoLoadingComponent,
GalleryGridComponent,
GalleryDirectoryComponent,
GalleryLightboxComponent,
GalleryMapComponent,
GalleryMapLightboxComponent,
FrameComponent,
GallerySearchComponent,
GalleryNavigatorComponent,
GalleryPhotoComponent,
FrameComponent,
StringifyRole],
providers: [
{provide: LAZY_MAPS_API_CONFIG, useClass: GoogleMapsConfig},
NetworkService,
UserService,
GalleryCacheService,
GalleryService,
AuthenticationService,
ThumbnailLoaderService,
ThumbnailManagerService,
FullScreenService,
OverlayService],
bootstrap: [AppComponent]
bootstrap: [AppComponent]
})
export class AppModule {
}
}

View File

@ -1,31 +1,31 @@
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";
const ROUTES: Routes = [
{
path: 'login',
component: LoginComponent
},
{
path: 'admin',
component: AdminComponent
},
{
path: 'gallery/:directory',
component: GalleryComponent
},
{
path: 'gallery',
component: GalleryComponent
},
{
path: 'search/:searchText',
component: GalleryComponent
},
{path: '', redirectTo: '/login', pathMatch: 'full'}
{
path: 'login',
component: LoginComponent
},
{
path: 'admin',
component: AdminComponent
},
{
path: 'gallery/:directory',
component: GalleryComponent
},
{
path: 'gallery',
component: GalleryComponent
},
{
path: 'search/:searchText',
component: GalleryComponent
},
{path: '', redirectTo: '/login', pathMatch: 'full'}
];
export const appRoutes: ModuleWithProviders = RouterModule.forRoot(ROUTES);

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">
@ -29,4 +29,4 @@
</div><!--/.nav-collapse -->
</div>
</nav>
<ng-content select="[body]"></ng-content>
<ng-content select="[body]"></ng-content>

View File

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

View File

@ -3,40 +3,40 @@ import {Utils} from "../../../common/Utils";
export class IconPhoto {
protected replacementSizeCache: number|boolean = false;
protected replacementSizeCache: number | boolean = false;
constructor(public photo: PhotoDTO) {
constructor(public photo: PhotoDTO) {
}
iconLoaded() {
this.photo.readyIcon = true;
}
isIconAvailable() {
return this.photo.readyIcon;
}
getIconPath() {
return Utils.concatUrls("/api/gallery/content/", this.photo.directory.path, this.photo.directory.name, this.photo.name, "icon");
}
getPhotoPath() {
return Utils.concatUrls("/api/gallery/content/", this.photo.directory.path, this.photo.directory.name, this.photo.name);
}
equals(other: PhotoDTO | IconPhoto): boolean {
//is gridphoto
if (other instanceof IconPhoto) {
return this.photo.directory.path === other.photo.directory.path && this.photo.directory.name === other.photo.directory.name && this.photo.name === other.photo.name
}
iconLoaded() {
this.photo.readyIcon = true;
//is photo
if (other.directory) {
return this.photo.directory.path === other.directory.path && this.photo.directory.name === other.directory.name && this.photo.name === other.name
}
isIconAvailable() {
return this.photo.readyIcon;
}
getIconPath() {
return Utils.concatUrls("/api/gallery/content/", this.photo.directory.path, this.photo.directory.name, this.photo.name, "icon");
}
getPhotoPath() {
return Utils.concatUrls("/api/gallery/content/", this.photo.directory.path, this.photo.directory.name, this.photo.name);
}
equals(other: PhotoDTO | IconPhoto): boolean {
//is gridphoto
if (other instanceof IconPhoto) {
return this.photo.directory.path === other.photo.directory.path && this.photo.directory.name === other.photo.directory.name && this.photo.name === other.photo.name
}
//is photo
if (other.directory) {
return this.photo.directory.path === other.directory.path && this.photo.directory.name === other.directory.name && this.photo.name === other.name
}
return false;
}
}
return false;
}
}

View File

@ -5,59 +5,59 @@ import {Config} from "../../../common/config/public/Config";
export class Photo extends IconPhoto {
constructor(photo: PhotoDTO, public renderWidth: number, public renderHeight: number) {
super(photo);
constructor(photo: PhotoDTO, public renderWidth: number, public renderHeight: number) {
super(photo);
}
thumbnailLoaded() {
if (!this.isThumbnailAvailable()) {
this.photo.readyThumbnails = this.photo.readyThumbnails || [];
this.photo.readyThumbnails.push(this.getThumbnailSize());
}
}
getThumbnailSize() {
let renderSize = Math.sqrt(this.renderWidth * this.renderHeight);
return Utils.findClosest(renderSize, Config.Client.thumbnailSizes);
}
thumbnailLoaded() {
if (!this.isThumbnailAvailable()) {
this.photo.readyThumbnails = this.photo.readyThumbnails || [];
this.photo.readyThumbnails.push(this.getThumbnailSize());
getReplacementThumbnailSize(): number {
if (this.replacementSizeCache === false) {
this.replacementSizeCache = null;
let size = this.getThumbnailSize();
if (!!this.photo.readyThumbnails) {
for (let i = 0; i < this.photo.readyThumbnails.length; i++) {
if (this.photo.readyThumbnails[i] < size) {
this.replacementSizeCache = this.photo.readyThumbnails[i];
break;
}
}
}
}
return <number>this.replacementSizeCache;
}
getThumbnailSize() {
let renderSize = Math.sqrt(this.renderWidth * this.renderHeight);
return Utils.findClosest(renderSize, Config.Client.thumbnailSizes);
}
isReplacementThumbnailAvailable() {
return this.getReplacementThumbnailSize() !== null;
}
getReplacementThumbnailSize(): number {
isThumbnailAvailable() {
return this.photo.readyThumbnails && this.photo.readyThumbnails.indexOf(this.getThumbnailSize()) != -1;
}
if (this.replacementSizeCache === false) {
this.replacementSizeCache = null;
getReplacementThumbnailPath() {
let size = this.getReplacementThumbnailSize();
return Utils.concatUrls("/api/gallery/content/", this.photo.directory.path, this.photo.directory.name, this.photo.name, "thumbnail", size.toString());
let size = this.getThumbnailSize();
if (!!this.photo.readyThumbnails) {
for (let i = 0; i < this.photo.readyThumbnails.length; i++) {
if (this.photo.readyThumbnails[i] < size) {
this.replacementSizeCache = this.photo.readyThumbnails[i];
break;
}
}
}
}
return <number>this.replacementSizeCache;
}
}
isReplacementThumbnailAvailable() {
return this.getReplacementThumbnailSize() !== null;
}
isThumbnailAvailable() {
return this.photo.readyThumbnails && this.photo.readyThumbnails.indexOf(this.getThumbnailSize()) != -1;
}
getReplacementThumbnailPath() {
let size = this.getReplacementThumbnailSize();
return Utils.concatUrls("/api/gallery/content/", this.photo.directory.path, this.photo.directory.name, this.photo.name, "thumbnail", size.toString());
}
getThumbnailPath() {
let size = this.getThumbnailSize();
return Utils.concatUrls("/api/gallery/content/", this.photo.directory.path, this.photo.directory.name, this.photo.name, "thumbnail", size.toString());
}
getThumbnailPath() {
let size = this.getThumbnailSize();
return Utils.concatUrls("/api/gallery/content/", this.photo.directory.path, this.photo.directory.name, this.photo.name, "thumbnail", size.toString());
}
}
}

View File

@ -8,79 +8,79 @@ import {Config} from "../../../common/config/public/Config";
export class GalleryCacheService {
public getDirectory(directoryName: string): DirectoryDTO {
if (Config.Client.enableCache == false) {
return null;
}
let value = localStorage.getItem(directoryName);
if (value != null) {
let directory: DirectoryDTO = JSON.parse(value);
//Add references
let addDir = (dir: DirectoryDTO) => {
dir.photos.forEach((photo: PhotoDTO) => {
photo.directory = dir;
});
dir.directories.forEach((directory: DirectoryDTO) => {
addDir(directory);
directory.parent = dir;
});
};
addDir(directory);
return directory;
}
return null;
public getDirectory(directoryName: string): DirectoryDTO {
if (Config.Client.enableCache == false) {
return null;
}
let value = localStorage.getItem(directoryName);
if (value != null) {
let directory: DirectoryDTO = JSON.parse(value);
public setDirectory(directory: DirectoryDTO): void {
if (Config.Client.enableCache == false) {
return;
}
localStorage.setItem(Utils.concatUrls(directory.path, directory.name), JSON.stringify(directory));
directory.directories.forEach((dir: DirectoryDTO) => {
let name = Utils.concatUrls(dir.path, dir.name);
if (localStorage.getItem(name) == null) { //don't override existing
localStorage.setItem(Utils.concatUrls(dir.path, dir.name), JSON.stringify(dir));
}
//Add references
let addDir = (dir: DirectoryDTO) => {
dir.photos.forEach((photo: PhotoDTO) => {
photo.directory = dir;
});
dir.directories.forEach((directory: DirectoryDTO) => {
addDir(directory);
directory.parent = dir;
});
};
addDir(directory);
return directory;
}
return null;
}
public setDirectory(directory: DirectoryDTO): void {
if (Config.Client.enableCache == false) {
return;
}
/**
* Update photo state at cache too (Eg.: thumbnail rendered)
* @param photo
*/
public photoUpdated(photo: PhotoDTO): void {
localStorage.setItem(Utils.concatUrls(directory.path, directory.name), JSON.stringify(directory));
if (Config.Client.enableCache == false) {
return;
}
directory.directories.forEach((dir: DirectoryDTO) => {
let name = Utils.concatUrls(dir.path, dir.name);
if (localStorage.getItem(name) == null) { //don't override existing
localStorage.setItem(Utils.concatUrls(dir.path, dir.name), JSON.stringify(dir));
}
});
let directoryName = Utils.concatUrls(photo.directory.path, photo.directory.name);
let value = localStorage.getItem(directoryName);
if (value != null) {
let directory: DirectoryDTO = JSON.parse(value);
directory.photos.forEach((p) => {
if (p.name === photo.name) {
//update data
p.metadata = photo.metadata;
p.readyThumbnails = photo.readyThumbnails;
}
//save changes
localStorage.setItem(directoryName, JSON.stringify(directory));
return;
}
});
}
/**
* Update photo state at cache too (Eg.: thumbnail rendered)
* @param photo
*/
public photoUpdated(photo: PhotoDTO): void {
if (Config.Client.enableCache == false) {
return;
}
let directoryName = Utils.concatUrls(photo.directory.path, photo.directory.name);
let value = localStorage.getItem(directoryName);
if (value != null) {
let directory: DirectoryDTO = JSON.parse(value);
directory.photos.forEach((p) => {
if (p.name === photo.name) {
//update data
p.metadata = photo.metadata;
p.readyThumbnails = photo.readyThumbnails;
//save changes
localStorage.setItem(directoryName, JSON.stringify(directory));
return;
}
});
}
}
}

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";
@ -6,41 +6,41 @@ import {Photo} from "../Photo";
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'],
providers: [RouterLink],
selector: 'gallery-directory',
templateUrl: './directory.gallery.component.html',
styleUrls: ['./directory.gallery.component.css'],
providers: [RouterLink],
})
export class GalleryDirectoryComponent implements OnInit,OnDestroy {
@Input() directory: DirectoryDTO;
@ViewChild("dirContainer") container: ElementRef;
thumbnail: Thumbnail = null;
export class GalleryDirectoryComponent implements OnInit, OnDestroy {
@Input() directory: DirectoryDTO;
@ViewChild("dirContainer") container: ElementRef;
thumbnail: Thumbnail = null;
constructor(private thumbnailService: ThumbnailManagerService) {
}
ngOnInit() {
if (this.directory.photos.length > 0) {
this.thumbnail = this.thumbnailService.getThumbnail(new Photo(this.directory.photos[0], 100, 100));
constructor(private thumbnailService: ThumbnailManagerService) {
}
}
ngOnInit() {
if (this.directory.photos.length > 0) {
this.thumbnail = this.thumbnailService.getThumbnail(new Photo(this.directory.photos[0], 100, 100));
//TODO: implement scroll
isInView(): boolean {
return document.body.scrollTop < this.container.nativeElement.offsetTop + this.container.nativeElement.clientHeight
&& document.body.scrollTop + window.innerHeight > this.container.nativeElement.offsetTop;
}
}
}
//TODO: implement scroll
isInView(): boolean {
return document.body.scrollTop < this.container.nativeElement.offsetTop + this.container.nativeElement.clientHeight
&& document.body.scrollTop + window.innerHeight > this.container.nativeElement.offsetTop;
}
getDirectoryPath() {
return Utils.concatUrls(this.directory.path, this.directory.name);
}
ngOnDestroy() {
if (this.thumbnail != null) {
this.thumbnail.destroy();
}
getDirectoryPath() {
return Utils.concatUrls(this.directory.path, this.directory.name);
}
ngOnDestroy() {
if (this.thumbnail != null) {
this.thumbnail.destroy();
}
}
}

View File

@ -5,42 +5,42 @@ import {Event} from "../../../common/event/Event";
export class FullScreenService {
OnFullScreenChange = new Event<boolean>();
OnFullScreenChange = new Event<boolean>();
public isFullScreenEnabled(): boolean {
return !!(document.fullscreenElement || document['mozFullScreenElement'] || document.webkitFullscreenElement);
public isFullScreenEnabled(): boolean {
return !!(document.fullscreenElement || document['mozFullScreenElement'] || document.webkitFullscreenElement);
}
public showFullScreen(element: any) {
if (this.isFullScreenEnabled()) {
return;
}
public showFullScreen(element: any) {
if (this.isFullScreenEnabled()) {
return;
}
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
this.OnFullScreenChange.trigger(true);
}
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
this.OnFullScreenChange.trigger(true);
public exitFullScreen() {
if (!this.isFullScreenEnabled()) {
return;
}
public exitFullScreen() {
if (!this.isFullScreenEnabled()) {
return;
}
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document['mozCancelFullScreen']) {
document['mozCancelFullScreen']();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
this.OnFullScreenChange.trigger(false);
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document['mozCancelFullScreen']) {
document['mozCancelFullScreen']();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
this.OnFullScreenChange.trigger(false);
}
}

View File

@ -8,65 +8,63 @@ import {SearchTypes} from "../../../common/entities/AutoCompleteItem";
import {Config} from "../../../common/config/public/Config";
@Component({
selector: 'gallery',
templateUrl: 'app/gallery/gallery.component.html',
styleUrls: ['app/gallery/gallery.component.css']
selector: 'gallery',
templateUrl: './gallery.component.html',
styleUrls: ['./gallery.component.css']
})
export class GalleryComponent implements OnInit {
@ViewChild(GallerySearchComponent) search: GallerySearchComponent;
@ViewChild(GalleryGridComponent) grid: GalleryGridComponent;
@ViewChild(GallerySearchComponent) search: GallerySearchComponent;
@ViewChild(GalleryGridComponent) grid: GalleryGridComponent;
public showSearchBar: boolean = true;
public showSearchBar: boolean = true;
constructor(private _galleryService: GalleryService,
private _authService: AuthenticationService,
private _router: Router,
private _route: ActivatedRoute) {
constructor(public _galleryService: GalleryService,
private _authService: AuthenticationService,
private _router: Router,
private _route: ActivatedRoute) {
this.showSearchBar = Config.Client.Search.searchEnabled;
this.showSearchBar = Config.Client.Search.searchEnabled;
}
ngOnInit() {
if (!this._authService.isAuthenticated()) {
this._router.navigate(['login']);
return;
}
ngOnInit() {
if (!this._authService.isAuthenticated()) {
this._router.navigate(['login']);
this._route.params
.subscribe((params: Params) => {
let searchText = params['searchText'];
if (searchText && searchText != "") {
console.log("searching");
let typeString = params['type'];
if (typeString && typeString != "") {
console.log("with type");
let type: SearchTypes = <any>SearchTypes[typeString];
this._galleryService.search(searchText, type);
return;
}
this._galleryService.search(searchText);
return;
}
this._route.params
.subscribe((params: Params) => {
let searchText = params['searchText'];
if (searchText && searchText != "") {
console.log("searching");
let typeString = params['type'];
if (typeString && typeString != "") {
console.log("with type");
let type: SearchTypes = <any>SearchTypes[typeString];
this._galleryService.search(searchText, type);
return;
}
let directoryName = params['directory'];
directoryName = directoryName ? directoryName : "";
this._galleryService.search(searchText);
return;
}
this._galleryService.getDirectory(directoryName);
});
let directoryName = params['directory'];
directoryName = directoryName ? directoryName : "";
}
this._galleryService.getDirectory(directoryName);
});
}
onLightboxLastElement() {
this.grid.renderARow();
}
onLightboxLastElement() {
this.grid.renderARow();
}
}

View File

@ -10,104 +10,104 @@ import {GalleryCacheService} from "./cache.gallery.service";
@Injectable()
export class GalleryService {
public content: ContentWrapper;
private lastDirectory: DirectoryDTO;
private searchId: any;
public content: ContentWrapper;
private lastDirectory: DirectoryDTO;
private searchId: any;
constructor(private networkService: NetworkService, private galleryCacheService: GalleryCacheService) {
this.content = new ContentWrapper();
}
constructor(private networkService: NetworkService, private galleryCacheService: GalleryCacheService) {
this.content = new ContentWrapper();
}
lastRequest: {directory: string} = {
directory: null
};
lastRequest: { directory: string } = {
directory: null
};
public getDirectory(directoryName: string): Promise<Message<ContentWrapper>> {
this.content = new ContentWrapper();
public getDirectory(directoryName: string): Promise<Message<ContentWrapper>> {
this.content = new ContentWrapper();
this.content.directory = this.galleryCacheService.getDirectory(directoryName);
this.content.searchResult = null;
this.lastRequest.directory = directoryName;
return this.networkService.getJson("/gallery/content/" + directoryName).then(
(message: Message<ContentWrapper>) => {
if (!message.error && message.result) {
this.content.directory = this.galleryCacheService.getDirectory(directoryName);
this.content.searchResult = null;
this.lastRequest.directory = directoryName;
return this.networkService.getJson("/gallery/content/" + directoryName).then(
(message: Message<ContentWrapper>) => {
if (!message.error && message.result) {
this.galleryCacheService.setDirectory(message.result.directory); //save it before adding references
this.galleryCacheService.setDirectory(message.result.directory); //save it before adding references
if (this.lastRequest.directory != directoryName) {
return;
}
if (this.lastRequest.directory != directoryName) {
return;
}
//Add references
let addDir = (dir: DirectoryDTO) => {
dir.photos.forEach((photo: PhotoDTO) => {
photo.directory = dir;
});
dir.directories.forEach((directory: DirectoryDTO) => {
addDir(directory);
directory.parent = dir;
});
};
addDir(message.result.directory);
this.lastDirectory = message.result.directory;
this.content = message.result;
}
return message;
});
}
//TODO: cache
public search(text: string, type?: SearchTypes): Promise<Message<ContentWrapper>> {
clearTimeout(this.searchId);
if (text === null || text === '') {
return Promise.resolve(new Message(null, null));
}
let queryString = "/search/" + text;
if (type) {
queryString += "?type=" + type;
}
return this.networkService.getJson(queryString).then(
(message: Message<ContentWrapper>) => {
if (!message.error && message.result) {
this.content = message.result;
}
return message;
});
}
//TODO: cache (together with normal search)
public instantSearch(text: string): Promise<Message<ContentWrapper>> {
if (text === null || text === '') {
this.content.directory = this.lastDirectory;
this.content.searchResult = null;
clearTimeout(this.searchId);
return Promise.resolve(new Message(null, null));
}
if (this.searchId != null) {
clearTimeout(this.searchId);
}
this.searchId = setTimeout(() => {
this.search(text);
this.searchId = null;
}, 3000); //TODO: set timeout to config
return this.networkService.getJson("/instant-search/" + text).then(
(message: Message<ContentWrapper>) => {
if (!message.error && message.result) {
this.content = message.result;
}
return message;
//Add references
let addDir = (dir: DirectoryDTO) => {
dir.photos.forEach((photo: PhotoDTO) => {
photo.directory = dir;
});
dir.directories.forEach((directory: DirectoryDTO) => {
addDir(directory);
directory.parent = dir;
});
};
addDir(message.result.directory);
this.lastDirectory = message.result.directory;
this.content = message.result;
}
return message;
});
}
//TODO: cache
public search(text: string, type?: SearchTypes): Promise<Message<ContentWrapper>> {
clearTimeout(this.searchId);
if (text === null || text === '') {
return Promise.resolve(new Message(null, null));
}
let queryString = "/search/" + text;
if (type) {
queryString += "?type=" + type;
}
return this.networkService.getJson(queryString).then(
(message: Message<ContentWrapper>) => {
if (!message.error && message.result) {
this.content = message.result;
}
return message;
});
}
//TODO: cache (together with normal search)
public instantSearch(text: string): Promise<Message<ContentWrapper>> {
if (text === null || text === '') {
this.content.directory = this.lastDirectory;
this.content.searchResult = null;
clearTimeout(this.searchId);
return Promise.resolve(new Message(null, null));
}
if (this.searchId != null) {
clearTimeout(this.searchId);
}
this.searchId = setTimeout(() => {
this.search(text);
this.searchId = null;
}, 3000); //TODO: set timeout to config
return this.networkService.getJson("/instant-search/" + text).then(
(message: Message<ContentWrapper>) => {
if (!message.error && message.result) {
this.content = message.result;
}
return message;
});
}
}

View File

@ -3,9 +3,9 @@ import {Photo} from "../Photo";
export class GridPhoto extends Photo {
constructor(photo: PhotoDTO, renderWidth: number, renderHeight: number, public rowId: number) {
super(photo, renderWidth, renderHeight);
}
constructor(photo: PhotoDTO, renderWidth: number, renderHeight: number, public rowId: number) {
super(photo, renderWidth, renderHeight);
}
}
}

View File

@ -2,63 +2,63 @@ import {PhotoDTO} from "../../../../common/entities/PhotoDTO";
export class GridRowBuilder {
private photoRow: Array<PhotoDTO> = [];
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;
constructor(private photos: Array<PhotoDTO>, private startIndex: number, private photoMargin: number, private containerWidth: number) {
this.photoIndex = startIndex;
}
public addPhotos(number: number) {
for (let i = 0; i < number; i++) {
this.addPhoto();
}
}
public addPhoto(): boolean {
if (this.photoIndex + 1 > this.photos.length) {
return false;
}
this.photoRow.push(this.photos[this.photoIndex]);
this.photoIndex++;
return true;
}
public removePhoto(): boolean {
if (this.photoIndex - 1 < this.startIndex) {
return false;
}
this.photoIndex--;
this.photoRow.pop();
return true;
}
public getPhotoRow(): Array<PhotoDTO> {
return this.photoRow;
}
public adjustRowHeightBetween(minHeight: number, maxHeight: number) {
while (this.calcRowHeight() > maxHeight && this.addPhoto() === true) { //row too high -> add more images
}
public addPhotos(number:number) {
for (let i = 0; i < number; i++) {
this.addPhoto();
}
while (this.calcRowHeight() < minHeight && this.removePhoto() === true) { //roo too small -> remove images
}
public addPhoto():boolean {
if (this.photoIndex + 1 > this.photos.length) {
return false;
}
this.photoRow.push(this.photos[this.photoIndex]);
this.photoIndex++;
return true;
//keep at least one photo int thr row
if (this.photoRow.length <= 0) {
this.addPhoto();
}
}
public removePhoto():boolean {
if (this.photoIndex - 1 < this.startIndex) {
return false;
}
this.photoIndex--;
this.photoRow.pop();
return true;
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
}
let height = (this.containerWidth - this.photoRow.length * (this.photoMargin * 2) - 1) / width; //cant be equal -> width-1
public getPhotoRow(): Array<PhotoDTO> {
return this.photoRow;
}
public adjustRowHeightBetween(minHeight:number, maxHeight:number) {
while (this.calcRowHeight() > maxHeight && this.addPhoto() === true) { //row too high -> add more images
}
while (this.calcRowHeight() < minHeight && this.removePhoto() === true) { //roo too small -> remove images
}
//keep at least one photo int thr row
if (this.photoRow.length <= 0) {
this.addPhoto();
}
}
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
}
let height = (this.containerWidth - this.photoRow.length * (this.photoMargin * 2) - 1) / width; //cant be equal -> width-1
return height + (this.photoMargin * 2);
};
}
return height + (this.photoMargin * 2);
};
}

View File

@ -1,14 +1,14 @@
import {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
HostListener,
Input,
OnChanges,
QueryList,
ViewChild,
ViewChildren
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
HostListener,
Input,
OnChanges,
QueryList,
ViewChild,
ViewChildren
} from "@angular/core";
import {PhotoDTO} from "../../../../common/entities/PhotoDTO";
import {GridRowBuilder} from "./GridRowBuilder";
@ -19,197 +19,197 @@ import {OverlayService} from "../overlay.service";
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'],
selector: 'gallery-grid',
templateUrl: './grid.gallery.component.html',
styleUrls: ['./grid.gallery.component.css'],
})
export class GalleryGridComponent implements OnChanges, AfterViewInit {
@ViewChild('gridContainer') gridContainer: ElementRef;
@ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList<GalleryPhotoComponent>;
@ViewChild('gridContainer') gridContainer: ElementRef;
@ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList<GalleryPhotoComponent>;
@Input() photos: Array<PhotoDTO>;
@Input() lightbox: GalleryLightboxComponent;
@Input() photos: Array<PhotoDTO>;
@Input() lightbox: GalleryLightboxComponent;
photosToRender: Array<GridPhoto> = [];
containerWidth: number = 0;
photosToRender: Array<GridPhoto> = [];
containerWidth: number = 0;
private IMAGE_MARGIN = 2;
private TARGET_COL_COUNT = 5;
private MIN_ROW_COUNT = 2;
private MAX_ROW_COUNT = 5;
private IMAGE_MARGIN = 2;
private TARGET_COL_COUNT = 5;
private MIN_ROW_COUNT = 2;
private MAX_ROW_COUNT = 5;
private onScrollFired = false;
private scrollbarWidth = 0;
private onScrollFired = false;
private scrollbarWidth = 0;
constructor(private overlayService: OverlayService, private changeDetector: ChangeDetectorRef) {
constructor(private overlayService: OverlayService, private changeDetector: ChangeDetectorRef) {
}
ngOnChanges() {
if (this.isAfterViewInit === false) {
return;
}
this.updateContainerWidth();
this.sortPhotos();
this.mergeNewPhotos();
setTimeout(() => {
this.renderPhotos();
}, 0);
}
@HostListener('window:resize')
onResize() {
if (this.isAfterViewInit === false) {
return;
}
this.updateContainerWidth();
this.sortPhotos();
//render the same amount of images on resize
let renderedIndex = this.renderedPhotoIndex;
this.clearRenderedPhotos();
this.renderPhotos(renderedIndex);
}
isAfterViewInit: boolean = false;
ngAfterViewInit() {
this.lightbox.setGridPhotoQL(this.gridPhotoQL);
//TODO: implement scroll detection
this.updateContainerWidth();
this.sortPhotos();
this.clearRenderedPhotos();
setTimeout(() => {
this.renderPhotos();
}, 0);
this.isAfterViewInit = true;
}
private sortPhotos() {
//sort pohots by date
this.photos.sort((a: PhotoDTO, b: PhotoDTO) => {
return a.metadata.creationDate - b.metadata.creationDate;
});
}
private clearRenderedPhotos() {
this.photosToRender = [];
this.renderedPhotoIndex = 0;
this.changeDetector.detectChanges();
}
private mergeNewPhotos() {
//merge new data with old one
let lastSameIndex = 0;
let lastRowId = null;
for (let i = 0; i < this.photos.length && i < this.photosToRender.length; i++) {
//thIf a photo changed the whole row has to be removed
if (this.photosToRender[i].rowId != lastRowId) {
lastSameIndex = i;
lastRowId = this.photosToRender[i].rowId;
}
if (this.photosToRender[i].equals(this.photos[i]) === false) {
break;
}
}
ngOnChanges() {
if (this.isAfterViewInit === false) {
return;
if (lastSameIndex > 0) {
this.photosToRender.splice(lastSameIndex, this.photosToRender.length - lastSameIndex);
this.renderedPhotoIndex = lastSameIndex;
} else {
this.clearRenderedPhotos();
}
}
private renderedPhotoIndex: number = 0;
private renderPhotos(numberOfPhotos: number = 0) {
if (this.containerWidth == 0 || this.renderedPhotoIndex >= this.photos.length || !this.shouldRenderMore()) {
return;
}
let renderedContentHeight = 0;
while (this.renderedPhotoIndex < this.photos.length && (this.shouldRenderMore(renderedContentHeight) === true || this.renderedPhotoIndex < numberOfPhotos)) {
let ret = this.renderARow();
if (ret === null) {
throw new Error("Gridphotos rendering failed");
}
renderedContentHeight += ret;
}
}
/**
* Returns true, if scroll is >= 70% to render more images.
* Or of onscroll renderin is off: return always to render all the images at once
* @param offset Add height to the client height (conent is not yet added to the dom, but calculate with it)
* @returns {boolean}
*/
private shouldRenderMore(offset: number = 0): boolean {
return Config.Client.enableOnScrollRendering === false ||
window.scrollY >= (document.body.clientHeight + offset - window.innerHeight) * 0.7
|| (document.body.clientHeight + offset) * 0.85 < window.innerHeight;
}
@HostListener('window:scroll')
onScroll() {
if (!this.onScrollFired) {
window.requestAnimationFrame(() => {
this.renderPhotos();
if (Config.Client.enableOnScrollThumbnailPrioritising === true) {
this.gridPhotoQL.toArray().forEach((pc: GalleryPhotoComponent) => {
pc.onScroll();
});
}
this.updateContainerWidth();
this.sortPhotos();
this.mergeNewPhotos();
setImmediate(() => {
this.renderPhotos();
});
this.onScrollFired = false;
});
this.onScrollFired = true;
}
}
@HostListener('window:resize')
onResize() {
if (this.isAfterViewInit === false) {
return;
}
this.updateContainerWidth();
this.sortPhotos();
//render the same amount of images on resize
let renderedIndex = this.renderedPhotoIndex;
this.clearRenderedPhotos();
this.renderPhotos(renderedIndex);
public renderARow(): number {
if (this.renderedPhotoIndex >= this.photos.length) {
return null;
}
isAfterViewInit: boolean = false;
let maxRowHeight = window.innerHeight / this.MIN_ROW_COUNT;
let minRowHeight = window.innerHeight / this.MAX_ROW_COUNT;
ngAfterViewInit() {
this.lightbox.setGridPhotoQL(this.gridPhotoQL);
let photoRowBuilder = new GridRowBuilder(this.photos, this.renderedPhotoIndex, this.IMAGE_MARGIN, this.containerWidth - this.overlayService.getPhantomScrollbarWidth());
photoRowBuilder.addPhotos(this.TARGET_COL_COUNT);
photoRowBuilder.adjustRowHeightBetween(minRowHeight, maxRowHeight);
//TODO: implement scroll detection
let rowHeight = photoRowBuilder.calcRowHeight();
let imageHeight = rowHeight - (this.IMAGE_MARGIN * 2);
photoRowBuilder.getPhotoRow().forEach((photo) => {
let imageWidth = imageHeight * (photo.metadata.size.width / photo.metadata.size.height);
this.photosToRender.push(new GridPhoto(photo, imageWidth, imageHeight, this.renderedPhotoIndex));
});
this.updateContainerWidth();
this.sortPhotos();
this.clearRenderedPhotos();
setImmediate(() => {
this.renderPhotos();
});
this.isAfterViewInit = true;
}
private sortPhotos() {
//sort pohots by date
this.photos.sort((a: PhotoDTO, b: PhotoDTO) => {
return a.metadata.creationDate - b.metadata.creationDate;
});
}
private clearRenderedPhotos() {
this.photosToRender = [];
this.renderedPhotoIndex = 0;
this.changeDetector.detectChanges();
}
private mergeNewPhotos() {
//merge new data with old one
let lastSameIndex = 0;
let lastRowId = null;
for (let i = 0; i < this.photos.length && i < this.photosToRender.length; i++) {
//thIf a photo changed the whole row has to be removed
if (this.photosToRender[i].rowId != lastRowId) {
lastSameIndex = i;
lastRowId = this.photosToRender[i].rowId;
}
if (this.photosToRender[i].equals(this.photos[i]) === false) {
break;
}
}
if (lastSameIndex > 0) {
this.photosToRender.splice(lastSameIndex, this.photosToRender.length - lastSameIndex);
this.renderedPhotoIndex = lastSameIndex;
} else {
this.clearRenderedPhotos();
}
}
private renderedPhotoIndex: number = 0;
private renderPhotos(numberOfPhotos: number = 0) {
if (this.containerWidth == 0 || this.renderedPhotoIndex >= this.photos.length || !this.shouldRenderMore()) {
return;
}
let renderedContentHeight = 0;
while (this.renderedPhotoIndex < this.photos.length && (this.shouldRenderMore(renderedContentHeight) === true || this.renderedPhotoIndex < numberOfPhotos)) {
let ret = this.renderARow();
if (ret === null) {
throw new Error("Gridphotos rendering failed");
}
renderedContentHeight += ret;
}
}
/**
* Returns true, if scroll is >= 70% to render more images.
* Or of onscroll renderin is off: return always to render all the images at once
* @param offset Add height to the client height (conent is not yet added to the dom, but calculate with it)
* @returns {boolean}
*/
private shouldRenderMore(offset: number = 0): boolean {
return Config.Client.enableOnScrollRendering === false ||
window.scrollY >= (document.body.clientHeight + offset - window.innerHeight) * 0.7
|| (document.body.clientHeight + offset) * 0.85 < window.innerHeight;
}
@HostListener('window:scroll')
onScroll() {
if (!this.onScrollFired) {
window.requestAnimationFrame(() => {
this.renderPhotos();
if (Config.Client.enableOnScrollThumbnailPrioritising === true) {
this.gridPhotoQL.toArray().forEach((pc: GalleryPhotoComponent) => {
pc.onScroll();
});
}
this.onScrollFired = false;
});
this.onScrollFired = true;
}
}
public renderARow(): number {
if (this.renderedPhotoIndex >= this.photos.length) {
return null;
}
let maxRowHeight = window.innerHeight / this.MIN_ROW_COUNT;
let minRowHeight = window.innerHeight / this.MAX_ROW_COUNT;
let photoRowBuilder = new GridRowBuilder(this.photos, this.renderedPhotoIndex, this.IMAGE_MARGIN, this.containerWidth - this.overlayService.getPhantomScrollbarWidth());
photoRowBuilder.addPhotos(this.TARGET_COL_COUNT);
photoRowBuilder.adjustRowHeightBetween(minRowHeight, maxRowHeight);
let rowHeight = photoRowBuilder.calcRowHeight();
let imageHeight = rowHeight - (this.IMAGE_MARGIN * 2);
photoRowBuilder.getPhotoRow().forEach((photo) => {
let imageWidth = imageHeight * (photo.metadata.size.width / photo.metadata.size.height);
this.photosToRender.push(new GridPhoto(photo, imageWidth, imageHeight, this.renderedPhotoIndex));
});
this.renderedPhotoIndex += photoRowBuilder.getPhotoRow().length;
return rowHeight;
}
private updateContainerWidth(): number {
if (!this.gridContainer) {
return;
}
this.containerWidth = this.gridContainer.nativeElement.clientWidth;
this.renderedPhotoIndex += photoRowBuilder.getPhotoRow().length;
return rowHeight;
}
private updateContainerWidth(): number {
if (!this.gridContainer) {
return;
}
this.containerWidth = this.gridContainer.nativeElement.clientWidth;
}
}

View File

@ -1,13 +1,13 @@
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'],
selector: 'gallery-grid-photo-loading',
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 -->
@ -30,4 +30,4 @@
</div>
</div>
</div>
</div>

View File

@ -7,152 +7,152 @@ import {Thumbnail, ThumbnailManagerService} from "../../thumnailManager.service"
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'],
providers: [RouterLink],
selector: 'gallery-grid-photo',
templateUrl: './photo.grid.gallery.component.html',
styleUrls: ['./photo.grid.gallery.component.css'],
providers: [RouterLink],
})
export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
@Input() gridPhoto: GridPhoto;
@ViewChild("img") imageRef: ElementRef;
@ViewChild("info") infoDiv: ElementRef;
@ViewChild("photoContainer") container: ElementRef;
export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
@Input() gridPhoto: GridPhoto;
@ViewChild("img") imageRef: ElementRef;
@ViewChild("info") infoDiv: ElementRef;
@ViewChild("photoContainer") container: ElementRef;
thumbnail: Thumbnail;
/*
image = {
src: '',
show: false
};
thumbnail: Thumbnail;
/*
image = {
src: '',
show: false
};
loading = {
animate: false,
show: true
};
*/
loading = {
animate: false,
show: true
};
*/
infoStyle = {
height: 0,
background: "rgba(0,0,0,0.0)"
};
infoStyle = {
height: 0,
background: "rgba(0,0,0,0.0)"
};
SearchTypes: any = [];
searchEnabled: boolean = true;
SearchTypes: any = [];
searchEnabled: boolean = true;
wasInView: boolean = null;
wasInView: boolean = null;
constructor(private thumbnailService: ThumbnailManagerService) {
this.SearchTypes = SearchTypes;
this.searchEnabled = Config.Client.Search.searchEnabled;
}
constructor(private thumbnailService: ThumbnailManagerService) {
this.SearchTypes = SearchTypes;
this.searchEnabled = Config.Client.Search.searchEnabled;
}
ngOnInit() {
this.thumbnail = this.thumbnailService.getThumbnail(this.gridPhoto);
/* this.loading.show = true;
//set up before adding task to thumbnail generator
if (this.gridPhoto.isThumbnailAvailable()) {
this.image.src = this.gridPhoto.getThumbnailPath();
this.image.show = true;
} else if (this.gridPhoto.isReplacementThumbnailAvailable()) {
this.image.src = this.gridPhoto.getReplacementThumbnailPath();
this.image.show = true;
}*/
}
/*
ngAfterViewInit() {
//schedule change after Angular checks the model
if (!this.gridPhoto.isThumbnailAvailable()) {
setImmediate(() => {
let listener: ThumbnailLoadingListener = {
onStartedLoading: () => { //onLoadStarted
this.loading.animate = true;
},
onLoad: () => {//onLoaded
ngOnInit() {
this.thumbnail = this.thumbnailService.getThumbnail(this.gridPhoto);
/* this.loading.show = true;
//set up before adding task to thumbnail generator
if (this.gridPhoto.isThumbnailAvailable()) {
this.image.src = this.gridPhoto.getThumbnailPath();
this.image.show = true;
this.loading.show = false;
this.thumbnailTask = null;
},
onError: (error) => {//onError
this.thumbnailTask = null;
//TODO: handle error
//TODO: not an error if its from cache
console.error("something bad happened");
console.error(error);
}
};
if (this.gridPhoto.isReplacementThumbnailAvailable()) {
this.thumbnailTask = this.thumbnailService.loadImage(this.gridPhoto, ThumbnailLoadingPriority.medium, listener);
} else {
this.thumbnailTask = this.thumbnailService.loadImage(this.gridPhoto, ThumbnailLoadingPriority.high, listener);
}
});
}
} else if (this.gridPhoto.isReplacementThumbnailAvailable()) {
this.image.src = this.gridPhoto.getReplacementThumbnailPath();
this.image.show = true;
}*/
ngOnDestroy() {
this.thumbnail.destroy();
/*
if (this.thumbnailTask != null) {
this.thumbnailService.removeTask(this.thumbnailTask);
this.thumbnailTask = null;
}*/
}
}
/*
ngAfterViewInit() {
//schedule change after Angular checks the model
if (!this.gridPhoto.isThumbnailAvailable()) {
setImmediate(() => {
let listener: ThumbnailLoadingListener = {
onStartedLoading: () => { //onLoadStarted
this.loading.animate = true;
},
onLoad: () => {//onLoaded
this.image.src = this.gridPhoto.getThumbnailPath();
this.image.show = true;
this.loading.show = false;
this.thumbnailTask = null;
},
onError: (error) => {//onError
this.thumbnailTask = null;
//TODO: handle error
//TODO: not an error if its from cache
console.error("something bad happened");
console.error(error);
}
};
if (this.gridPhoto.isReplacementThumbnailAvailable()) {
this.thumbnailTask = this.thumbnailService.loadImage(this.gridPhoto, ThumbnailLoadingPriority.medium, listener);
} else {
this.thumbnailTask = this.thumbnailService.loadImage(this.gridPhoto, ThumbnailLoadingPriority.high, listener);
}
isInView(): boolean {
return document.body.scrollTop < this.container.nativeElement.offsetTop + this.container.nativeElement.clientHeight
&& document.body.scrollTop + window.innerHeight > this.container.nativeElement.offsetTop;
}
onScroll() {
let isInView = this.isInView();
if (this.wasInView != isInView) {
this.wasInView = isInView;
this.thumbnail.Visible = isInView;
}
}
getPositionText(): string {
if (!this.gridPhoto) {
return ""
}
return this.gridPhoto.photo.metadata.positionData.city ||
this.gridPhoto.photo.metadata.positionData.state ||
this.gridPhoto.photo.metadata.positionData.country;
}
hover() {
this.infoStyle.height = this.infoDiv.nativeElement.clientHeight;
this.infoStyle.background = "rgba(0,0,0,0.8)";
}
mouseOut() {
this.infoStyle.height = 0;
this.infoStyle.background = "rgba(0,0,0,0.0)";
}
});
}
}*/
ngOnDestroy() {
this.thumbnail.destroy();
/*
onImageLoad() {
this.loading.show = false;
}
*/
public getDimension(): Dimension {
return <Dimension>{
top: this.imageRef.nativeElement.offsetTop,
left: this.imageRef.nativeElement.offsetLeft,
width: this.imageRef.nativeElement.width,
height: this.imageRef.nativeElement.height
};
if (this.thumbnailTask != null) {
this.thumbnailService.removeTask(this.thumbnailTask);
this.thumbnailTask = null;
}*/
}
isInView(): boolean {
return document.body.scrollTop < this.container.nativeElement.offsetTop + this.container.nativeElement.clientHeight
&& document.body.scrollTop + window.innerHeight > this.container.nativeElement.offsetTop;
}
onScroll() {
let isInView = this.isInView();
if (this.wasInView != isInView) {
this.wasInView = isInView;
this.thumbnail.Visible = isInView;
}
}
getPositionText(): string {
if (!this.gridPhoto) {
return ""
}
return this.gridPhoto.photo.metadata.positionData.city ||
this.gridPhoto.photo.metadata.positionData.state ||
this.gridPhoto.photo.metadata.positionData.country;
}
hover() {
this.infoStyle.height = this.infoDiv.nativeElement.clientHeight;
this.infoStyle.background = "rgba(0,0,0,0.8)";
}
mouseOut() {
this.infoStyle.height = 0;
this.infoStyle.background = "rgba(0,0,0,0.0)";
}
/*
onImageLoad() {
this.loading.show = false;
}
*/
public getDimension(): Dimension {
return <Dimension>{
top: this.imageRef.nativeElement.offsetTop,
left: this.imageRef.nativeElement.offsetLeft,
width: this.imageRef.nativeElement.width,
height: this.imageRef.nativeElement.height
};
}
}

View File

@ -1,12 +1,12 @@
import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostListener,
Output,
QueryList,
ViewChild
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostListener,
Output,
QueryList,
ViewChild
} from "@angular/core";
import {PhotoDTO} from "../../../../common/entities/PhotoDTO";
import {GalleryPhotoComponent} from "../grid/photo/photo.grid.gallery.component";
@ -16,226 +16,226 @@ import {OverlayService} from "../overlay.service";
import {Subscription} from "rxjs";
@Component({
selector: 'gallery-lightbox',
styleUrls: ['app/gallery/lightbox/lightbox.gallery.component.css'],
templateUrl: 'app/gallery/lightbox/lightbox.gallery.component.html',
selector: 'gallery-lightbox',
styleUrls: ['./lightbox.gallery.component.css'],
templateUrl: './lightbox.gallery.component.html',
})
export class GalleryLightboxComponent {
@Output('onLastElement') onLastElement = new EventEmitter();
@Output('onLastElement') onLastElement = new EventEmitter();
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 blackCanvasOpacity: any = 0;
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};
public transition: string = "";
public blackCanvasOpacity: any = 0;
private activePhoto: GalleryPhotoComponent;
private gridPhotoQL: QueryList<GalleryPhotoComponent>;
public activePhoto: GalleryPhotoComponent;
private gridPhotoQL: QueryList<GalleryPhotoComponent>;
private visible = false;
private changeSubscription: Subscription = null;
public visible = false;
private changeSubscription: Subscription = null;
@ViewChild("root") elementRef: ElementRef;
@ViewChild("root") elementRef: ElementRef;
constructor(private fullScreenService: FullScreenService, private changeDetector: ChangeDetectorRef, private overlayService: OverlayService) {
constructor(public fullScreenService: FullScreenService, private changeDetector: ChangeDetectorRef, private overlayService: OverlayService) {
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:resize', ['$event'])
onResize() {
if (this.activePhoto) {
this.disableAnimation();
this.lightboxDimension.width = this.getScreenWidth();
this.lightboxDimension.height = this.getScreenHeight();
this.updateActivePhoto(this.activePhotoId);
}
}
public nextImage() {
this.disableAnimation();
if (this.activePhotoId + 1 < this.gridPhotoQL.length) {
this.showPhoto(this.activePhotoId + 1);
if (this.activePhotoId + 3 >= this.gridPhotoQL.length) {
this.onLastElement.emit({}); //trigger to render more photos if there are
}
return;
}
console.warn("can't find photo to show next");
}
public prevImage() {
this.disableAnimation();
if (this.activePhotoId > 0) {
this.showPhoto(this.activePhotoId - 1);
return;
}
console.warn("can't find photo to show prev");
}
activePhotoId: number = null;
private showPhoto(photoIndex: number) {
this.activePhoto = null;
this.changeDetector.detectChanges();
this.updateActivePhoto(photoIndex);
}
private updateActivePhoto(photoIndex: number) {
let pcList = this.gridPhotoQL.toArray();
if (photoIndex < 0 || photoIndex > this.gridPhotoQL.length) {
throw new Error("Can't find the photo");
}
this.activePhotoId = photoIndex;
this.activePhoto = pcList[photoIndex];
this.photoDimension = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
this.navigation.hasPrev = photoIndex > 0;
this.navigation.hasNext = photoIndex + 1 < pcList.length;
let to = this.activePhoto.getDimension();
//if target image out of screen -> scroll to there
if (this.getBodyScrollTop() > to.top || this.getBodyScrollTop() + this.getScreenHeight() < to.top) {
this.setBodyScrollTop(to.top);
}
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:resize', ['$event'])
onResize() {
if (this.activePhoto) {
this.disableAnimation();
this.lightboxDimension.width = this.getScreenWidth();
this.lightboxDimension.height = this.getScreenHeight();
this.updateActivePhoto(this.activePhotoId);
}
public show(photo: PhotoDTO) {
this.enableAnimation();
this.visible = true;
let selectedPhoto = this.findPhotoComponent(photo);
if (selectedPhoto === null) {
throw new Error("Can't find Photo");
}
public nextImage() {
this.disableAnimation();
if (this.activePhotoId + 1 < this.gridPhotoQL.length) {
this.showPhoto(this.activePhotoId + 1);
if (this.activePhotoId + 3 >= this.gridPhotoQL.length) {
this.onLastElement.emit({}); //trigger to render more photos if there are
}
return;
}
console.warn("can't find photo to show next");
}
this.lightboxDimension = selectedPhoto.getDimension();
this.lightboxDimension.top -= this.getBodyScrollTop();
this.blackCanvasOpacity = 0;
this.photoDimension = selectedPhoto.getDimension();
public prevImage() {
this.disableAnimation();
//disable scroll
this.overlayService.showOverlay();
setImmediate(() => {
this.lightboxDimension = <Dimension>{
top: 0,
left: 0,
width: this.getScreenWidth(),
height: this.getScreenHeight()
};
this.blackCanvasOpacity = 1.0;
this.showPhoto(this.gridPhotoQL.toArray().indexOf(selectedPhoto));
});
}
public hide() {
this.enableAnimation();
this.fullScreenService.exitFullScreen();
this.lightboxDimension = this.activePhoto.getDimension();
this.lightboxDimension.top -= this.getBodyScrollTop();
this.blackCanvasOpacity = 0;
this.photoDimension = this.activePhoto.getDimension();
setTimeout(() => {
this.visible = false;
this.activePhoto = null;
this.overlayService.hideOverlay();
}, 500);
}
setGridPhotoQL(value: QueryList<GalleryPhotoComponent>) {
if (this.changeSubscription != null) {
this.changeSubscription.unsubscribe();
}
this.gridPhotoQL = value;
this.changeSubscription = this.gridPhotoQL.changes.subscribe(() => {
if (this.activePhotoId != null && this.gridPhotoQL.length > this.activePhotoId) {
this.updateActivePhoto(this.activePhotoId);
}
});
}
private findPhotoComponent(photo: any) {
let galleryPhotoComponents = this.gridPhotoQL.toArray();
for (let i = 0; i < galleryPhotoComponents.length; i++) {
if (galleryPhotoComponents[i].gridPhoto.photo == photo) {
return galleryPhotoComponents[i];
}
}
return null;
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:keydown', ['$event'])
onKeyPress(e: KeyboardEvent) {
if (this.visible != true) {
return;
}
let event: KeyboardEvent = window.event ? <any>window.event : e;
switch (event.keyCode) {
case 37:
if (this.activePhotoId > 0) {
this.showPhoto(this.activePhotoId - 1);
return;
this.prevImage();
}
console.warn("can't find photo to show prev");
}
activePhotoId: number = null;
private showPhoto(photoIndex: number) {
this.activePhoto = null;
this.changeDetector.detectChanges();
this.updateActivePhoto(photoIndex);
}
private updateActivePhoto(photoIndex: number) {
let pcList = this.gridPhotoQL.toArray();
if (photoIndex < 0 || photoIndex > this.gridPhotoQL.length) {
throw new Error("Can't find the photo");
break;
case 39:
if (this.activePhotoId < this.gridPhotoQL.length - 1) {
this.nextImage();
}
this.activePhotoId = photoIndex;
this.activePhoto = pcList[photoIndex];
this.photoDimension = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
this.navigation.hasPrev = photoIndex > 0;
this.navigation.hasNext = photoIndex + 1 < pcList.length;
let to = this.activePhoto.getDimension();
//if target image out of screen -> scroll to there
if (this.getBodyScrollTop() > to.top || this.getBodyScrollTop() + this.getScreenHeight() < to.top) {
this.setBodyScrollTop(to.top);
}
break;
case 27: //escape
this.hide();
break;
}
}
public show(photo: PhotoDTO) {
this.enableAnimation();
this.visible = true;
let selectedPhoto = this.findPhotoComponent(photo);
if (selectedPhoto === null) {
throw new Error("Can't find Photo");
}
private enableAnimation() {
this.transition = null;
}
this.lightboxDimension = selectedPhoto.getDimension();
this.lightboxDimension.top -= this.getBodyScrollTop();
this.blackCanvasOpacity = 0;
this.photoDimension = selectedPhoto.getDimension();
private disableAnimation() {
this.transition = "initial";
}
//disable scroll
this.overlayService.showOverlay();
setImmediate(() => {
this.lightboxDimension = <Dimension>{
top: 0,
left: 0,
width: this.getScreenWidth(),
height: this.getScreenHeight()
};
this.blackCanvasOpacity = 1.0;
this.showPhoto(this.gridPhotoQL.toArray().indexOf(selectedPhoto));
});
private getBodyScrollTop(): number {
return window.scrollY;
}
private setBodyScrollTop(value: number) {
window.scrollTo(window.scrollX, value);
}
private getScreenWidth() {
return window.innerWidth;
}
private getScreenHeight() {
return window.innerHeight;
}
private calcLightBoxPhotoDimension(photo: PhotoDTO): Dimension {
let width = 0;
let height = 0;
if (photo.metadata.size.height > photo.metadata.size.width) {
width = Math.round(photo.metadata.size.width * (this.getScreenHeight() / photo.metadata.size.height));
height = this.getScreenHeight();
} else {
width = this.getScreenWidth();
height = Math.round(photo.metadata.size.height * (this.getScreenWidth() / photo.metadata.size.width));
}
let top = (this.getScreenHeight() / 2 - height / 2);
let left = (this.getScreenWidth() / 2 - width / 2);
public hide() {
this.enableAnimation();
this.fullScreenService.exitFullScreen();
this.lightboxDimension = this.activePhoto.getDimension();
this.lightboxDimension.top -= this.getBodyScrollTop();
this.blackCanvasOpacity = 0;
this.photoDimension = this.activePhoto.getDimension();
setTimeout(() => {
this.visible = false;
this.activePhoto = null;
this.overlayService.hideOverlay();
}, 500);
}
setGridPhotoQL(value: QueryList<GalleryPhotoComponent>) {
if (this.changeSubscription != null) {
this.changeSubscription.unsubscribe();
}
this.gridPhotoQL = value;
this.changeSubscription = this.gridPhotoQL.changes.subscribe(() => {
if (this.activePhotoId != null && this.gridPhotoQL.length > this.activePhotoId) {
this.updateActivePhoto(this.activePhotoId);
}
});
}
private findPhotoComponent(photo: any) {
let galleryPhotoComponents = this.gridPhotoQL.toArray();
for (let i = 0; i < galleryPhotoComponents.length; i++) {
if (galleryPhotoComponents[i].gridPhoto.photo == photo) {
return galleryPhotoComponents[i];
}
}
return null;
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:keydown', ['$event'])
onKeyPress(e: KeyboardEvent) {
if (this.visible != true) {
return;
}
let event: KeyboardEvent = window.event ? <any>window.event : e;
switch (event.keyCode) {
case 37:
if (this.activePhotoId > 0) {
this.prevImage();
}
break;
case 39:
if (this.activePhotoId < this.gridPhotoQL.length - 1) {
this.nextImage();
}
break;
case 27: //escape
this.hide();
break;
}
}
private enableAnimation() {
this.transition = null;
}
private disableAnimation() {
this.transition = "initial";
}
private getBodyScrollTop(): number {
return window.scrollY;
}
private setBodyScrollTop(value: number) {
window.scrollTo(window.scrollX, value);
}
private getScreenWidth() {
return window.innerWidth;
}
private getScreenHeight() {
return window.innerHeight;
}
private calcLightBoxPhotoDimension(photo: PhotoDTO): Dimension {
let width = 0;
let height = 0;
if (photo.metadata.size.height > photo.metadata.size.width) {
width = Math.round(photo.metadata.size.width * (this.getScreenHeight() / photo.metadata.size.height));
height = this.getScreenHeight();
} else {
width = this.getScreenWidth();
height = Math.round(photo.metadata.size.height * (this.getScreenWidth() / photo.metadata.size.width));
}
let top = (this.getScreenHeight() / 2 - height / 2);
let left = (this.getScreenWidth() / 2 - width / 2);
return <Dimension>{top: top, left: left, width: width, height: height};
}
return <Dimension>{top: top, left: left, width: width, height: height};
}
}

View File

@ -2,64 +2,64 @@ import {Component, Input, OnChanges} from "@angular/core";
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'
selector: 'gallery-lightbox-photo',
styleUrls: ['./photo.lightbox.gallery.component.css'],
templateUrl: './photo.lightbox.gallery.component.html'
})
export class GalleryLightboxPhotoComponent implements OnChanges {
@Input() gridPhoto: GridPhoto;
@Input() gridPhoto: GridPhoto;
public imageSize = {width: "auto", height: "100"};
public imageSize = {width: "auto", height: "100"};
imageLoaded: boolean = false;
imageLoaded: boolean = false;
constructor() {
constructor() {
}
ngOnChanges() {
this.imageLoaded = false;
this.setImageSize();
}
private setImageSize() {
if (!this.gridPhoto) {
return;
}
ngOnChanges() {
this.imageLoaded = false;
this.setImageSize();
}
private setImageSize() {
if (!this.gridPhoto) {
return;
}
if (this.gridPhoto.photo.metadata.size.height > this.gridPhoto.photo.metadata.size.width) {
this.imageSize.height = "100";
this.imageSize.width = null;
} else {
this.imageSize.height = null;
this.imageSize.width = "100";
}
if (this.gridPhoto.photo.metadata.size.height > this.gridPhoto.photo.metadata.size.width) {
this.imageSize.height = "100";
this.imageSize.width = null;
} else {
this.imageSize.height = null;
this.imageSize.width = "100";
}
}
onImageLoad() {
this.imageLoaded = true;
}
onImageLoad() {
this.imageLoaded = true;
}
onImageError() {
//TODO:handle error
console.error("cant load image");
}
onImageError() {
//TODO:handle error
console.error("cant load image");
}
public showThumbnail(): boolean {
return this.gridPhoto && !this.imageLoaded &&
(this.gridPhoto.isThumbnailAvailable() || this.gridPhoto.isReplacementThumbnailAvailable());
}
public showThumbnail(): boolean {
return this.gridPhoto && !this.imageLoaded &&
(this.gridPhoto.isThumbnailAvailable() || this.gridPhoto.isReplacementThumbnailAvailable());
}
public thumbnailPath(): string {
if (this.gridPhoto.isThumbnailAvailable() === true)
return this.gridPhoto.getThumbnailPath();
public thumbnailPath(): string {
if (this.gridPhoto.isThumbnailAvailable() === true)
return this.gridPhoto.getThumbnailPath();
if (this.gridPhoto.isReplacementThumbnailAvailable() === true)
return this.gridPhoto.getReplacementThumbnailPath();
return null
}
if (this.gridPhoto.isReplacementThumbnailAvailable() === true)
return this.gridPhoto.getReplacementThumbnailPath();
return null
}
}

View File

@ -7,150 +7,150 @@ import {IconThumbnail, ThumbnailManagerService} from "../../thumnailManager.serv
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',
selector: 'gallery-map-lightbox',
styleUrls: ['./lightbox.map.gallery.component.css'],
templateUrl: './lightbox.map.gallery.component.html',
})
export class GalleryMapLightboxComponent implements OnChanges {
@Input() photos: Array<PhotoDTO>;
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}> = [];
mapCenter = {latitude: 0, longitude: 0};
@Input() photos: Array<PhotoDTO>;
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};
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;
@ViewChild("root") elementRef: ElementRef;
@ViewChild(AgmMap) map: AgmMap;
@ViewChild(AgmMap) map: AgmMap;
constructor(private fullScreenService: FullScreenService, private thumbnailService: ThumbnailManagerService) {
constructor(public fullScreenService: FullScreenService, private thumbnailService: ThumbnailManagerService) {
}
}
//TODO: fix zooming
ngOnChanges() {
if (this.visible == false) {
return;
}
this.showImages();
ngOnChanges() {
if (this.visible == false) {
return;
}
this.showImages();
}
public show(position: Dimension) {
this.visible = true;
this.opacity = 1.0;
this.startPosition = position;
this.lightboxDimension = position;
this.lightboxDimension.top -= this.getBodyScrollTop();
this.mapDimension = <Dimension>{
top: 0,
left: 0,
width: this.getScreenWidth(),
height: this.getScreenHeight()
};
this.map.triggerResize();
document.getElementsByTagName('body')[0].style.overflow = 'hidden';
this.showImages();
setImmediate(() => {
this.lightboxDimension = <Dimension>{
top: 0,
left: 0,
width: this.getScreenWidth(),
height: this.getScreenHeight()
};
});
}
public hide() {
this.fullScreenService.exitFullScreen();
let to = this.startPosition;
//iff target image out of screen -> scroll to there
if (this.getBodyScrollTop() > to.top || this.getBodyScrollTop() + this.getScreenHeight() < to.top) {
this.setBodyScrollTop(to.top);
}
public show(position: Dimension) {
this.visible = true;
this.opacity = 1.0;
this.startPosition = position;
this.lightboxDimension = position;
this.lightboxDimension.top -= this.getBodyScrollTop();
this.mapDimension = <Dimension>{
top: 0,
left: 0,
width: this.getScreenWidth(),
height: this.getScreenHeight()
this.lightboxDimension = this.startPosition;
this.lightboxDimension.top -= this.getBodyScrollTop();
document.getElementsByTagName('body')[0].style.overflow = 'scroll';
this.opacity = 0.0;
setTimeout(() => {
this.visible = false;
this.hideImages();
}, 500);
}
showImages() {
this.hideImages();
this.mapPhotos = this.photos.filter(p => {
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 } = {
latitude: p.metadata.positionData.GPSData.latitude,
longitude: p.metadata.positionData.GPSData.longitude,
thumbnail: th
};
if (th.Available == true) {
obj.iconUrl = th.Src;
} else {
th.OnLoad = () => {
obj.iconUrl = th.Src;
};
this.map.triggerResize();
}
return obj;
});
document.getElementsByTagName('body')[0].style.overflow = 'hidden';
this.showImages();
setImmediate(() => {
this.lightboxDimension = <Dimension>{
top: 0,
left: 0,
width: this.getScreenWidth(),
height: this.getScreenHeight()
};
});
if (this.mapPhotos.length > 0) {
this.mapCenter = this.mapPhotos[0];
}
}
public hide() {
this.fullScreenService.exitFullScreen();
let to = this.startPosition;
//iff target image out of screen -> scroll to there
if (this.getBodyScrollTop() > to.top || this.getBodyScrollTop() + this.getScreenHeight() < to.top) {
this.setBodyScrollTop(to.top);
}
this.lightboxDimension = this.startPosition;
this.lightboxDimension.top -= this.getBodyScrollTop();
document.getElementsByTagName('body')[0].style.overflow = 'scroll';
this.opacity = 0.0;
setTimeout(() => {
this.visible = false;
this.hideImages();
}, 500);
hideImages() {
this.mapPhotos.forEach(mp => mp.thumbnail.destroy());
this.mapPhotos = [];
}
private getBodyScrollTop(): number {
return window.scrollY;
}
private setBodyScrollTop(value: number) {
window.scrollTo(window.scrollX, value);
}
private getScreenWidth() {
return window.innerWidth;
}
private getScreenHeight() {
return window.innerHeight;
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:keydown', ['$event'])
onKeyPress(e: KeyboardEvent) {
if (this.visible != true) {
return;
}
showImages() {
this.hideImages();
this.mapPhotos = this.photos.filter(p => {
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} = {
latitude: p.metadata.positionData.GPSData.latitude,
longitude: p.metadata.positionData.GPSData.longitude,
thumbnail: th
};
if (th.Available == true) {
obj.iconUrl = th.Src;
} else {
th.OnLoad = () => {
obj.iconUrl = th.Src;
};
}
return obj;
});
if (this.mapPhotos.length > 0) {
this.mapCenter = this.mapPhotos[0];
}
}
hideImages() {
this.mapPhotos.forEach(mp => mp.thumbnail.destroy());
this.mapPhotos = [];
}
private getBodyScrollTop(): number {
return window.scrollY;
}
private setBodyScrollTop(value: number) {
window.scrollTo(window.scrollX, value);
}
private getScreenWidth() {
return window.innerWidth;
}
private getScreenHeight() {
return window.innerHeight;
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:keydown', ['$event'])
onKeyPress(e: KeyboardEvent) {
if (this.visible != true) {
return;
}
let event: KeyboardEvent = window.event ? <any>window.event : e;
switch (event.keyCode) {
case 27: //escape
this.hide();
break;
}
let event: KeyboardEvent = window.event ? <any>window.event : e;
switch (event.keyCode) {
case 27: //escape
this.hide();
break;
}
}
}

View File

@ -1,50 +1,50 @@
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']
selector: 'gallery-map',
templateUrl: './map.gallery.component.html',
styleUrls: ['./map.gallery.component.css']
})
export class GalleryMapComponent implements OnChanges, IRenderable {
@Input() photos: Array<PhotoDTO>;
@ViewChild(GalleryMapLightboxComponent) mapLightbox: GalleryMapLightboxComponent;
@Input() photos: Array<PhotoDTO>;
@ViewChild(GalleryMapLightboxComponent) mapLightbox: GalleryMapLightboxComponent;
mapPhotos: Array<{latitude: number, longitude: number}> = [];
mapCenter = {latitude: 0, longitude: 0};
@ViewChild("map") map: ElementRef;
//TODO: fix zooming
ngOnChanges() {
this.mapPhotos = this.photos.filter(p => {
return p.metadata && p.metadata.positionData && p.metadata.positionData.GPSData;
}).map(p => {
return {
latitude: p.metadata.positionData.GPSData.latitude,
longitude: p.metadata.positionData.GPSData.longitude
};
});
if (this.mapPhotos.length > 0) {
this.mapCenter = this.mapPhotos[0];
}
mapPhotos: Array<{ latitude: number, longitude: number }> = [];
mapCenter = {latitude: 0, longitude: 0};
@ViewChild("map") map: ElementRef;
//TODO: fix zooming
ngOnChanges() {
this.mapPhotos = this.photos.filter(p => {
return p.metadata && p.metadata.positionData && p.metadata.positionData.GPSData;
}).map(p => {
return {
latitude: p.metadata.positionData.GPSData.latitude,
longitude: p.metadata.positionData.GPSData.longitude
};
});
if (this.mapPhotos.length > 0) {
this.mapCenter = this.mapPhotos[0];
}
click() {
this.mapLightbox.show(this.getDimension());
}
public getDimension(): Dimension {
return <Dimension>{
top: this.map.nativeElement.offsetTop,
left: this.map.nativeElement.offsetLeft,
width: this.map.nativeElement.offsetWidth,
height: this.map.nativeElement.offsetHeight
};
}
}
click() {
this.mapLightbox.show(this.getDimension());
}
public getDimension(): Dimension {
return <Dimension>{
top: this.map.nativeElement.offsetTop,
left: this.map.nativeElement.offsetLeft,
width: this.map.nativeElement.offsetWidth,
height: this.map.nativeElement.offsetHeight
};
}
}

View File

@ -3,68 +3,68 @@ import {DirectoryDTO} from "../../../../common/entities/DirectoryDTO";
import {RouterLink} from "@angular/router";
@Component({
selector: 'gallery-navbar',
templateUrl: 'app/gallery/navigator/navigator.gallery.component.html',
providers: [RouterLink],
selector: 'gallery-navbar',
templateUrl: './navigator.gallery.component.html',
providers: [RouterLink],
})
export class GalleryNavigatorComponent implements OnChanges {
@Input() directory: DirectoryDTO;
@Input() directory: DirectoryDTO;
routes: Array<any> = [];
routes: Array<any> = [];
constructor() {
constructor() {
}
ngOnChanges() {
this.getPath();
}
getPath(): any {
if (!this.directory) {
return [];
}
let path = this.directory.path.replace(new RegExp("\\\\", 'g'), "/");
let dirs = path.split("/");
dirs.push(this.directory.name);
//removing empty strings
for (let i = 0; i < dirs.length; i++) {
if (!dirs[i] || 0 === dirs[i].length || "." === dirs[i]) {
dirs.splice(i, 1);
i--;
}
}
ngOnChanges() {
this.getPath();
}
getPath(): any {
if (!this.directory) {
return [];
}
let path = this.directory.path.replace(new RegExp("\\\\", 'g'), "/");
let dirs = path.split("/");
dirs.push(this.directory.name);
//removing empty strings
for (let i = 0; i < dirs.length; i++) {
if (!dirs[i] || 0 === dirs[i].length || "." === dirs[i]) {
dirs.splice(i, 1);
i--;
}
}
let arr: any = [];
//create root link
if (dirs.length == 0) {
arr.push({name: "Images", route: null});
} else {
arr.push({name: "Images", route: "/"});
}
//create rest navigation
dirs.forEach((name, index) => {
let route = dirs.slice(0, dirs.indexOf(name) + 1).join("/");
if (dirs.length - 1 == index) {
arr.push({name: name, route: null});
} else {
arr.push({name: name, route: route});
}
});
this.routes = arr;
let arr: any = [];
//create root link
if (dirs.length == 0) {
arr.push({name: "Images", route: null});
} else {
arr.push({name: "Images", route: "/"});
}
//create rest navigation
dirs.forEach((name, index) => {
let route = dirs.slice(0, dirs.indexOf(name) + 1).join("/");
if (dirs.length - 1 == index) {
arr.push({name: name, route: null});
} else {
arr.push({name: name, route: route});
}
});
this.routes = arr;
}
}

View File

@ -4,57 +4,57 @@ import {Event} from "../../../common/event/Event";
@Injectable()
export class OverlayService {
OnOverlayChange = new Event<boolean>();
private scrollWidth: number = null;
OnOverlayChange = new Event<boolean>();
private scrollWidth: number = null;
public showOverlay() {
public showOverlay() {
//disable scrolling
document.getElementsByTagName('body')[0].style.overflow = 'hidden';
this.OnOverlayChange.trigger(true);
//disable scrolling
document.getElementsByTagName('body')[0].style.overflow = 'hidden';
this.OnOverlayChange.trigger(true);
}
public hideOverlay() {
document.getElementsByTagName('body')[0].style.overflowY = 'scroll';
this.OnOverlayChange.trigger(false);
}
getScrollbarWidth() {
if (this.scrollWidth == null) {
let outer = document.createElement("div");
outer.style.visibility = "hidden";
outer.style.width = "100px";
outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
document.body.appendChild(outer);
let widthNoScroll = outer.offsetWidth;
// force scrollbars
outer.style.overflow = "scroll";
// add innerdiv
let inner = document.createElement("div");
inner.style.width = "100%";
outer.appendChild(inner);
let widthWithScroll = inner.offsetWidth;
// remove divs
outer.parentNode.removeChild(outer);
this.scrollWidth = widthNoScroll - widthWithScroll;
}
public hideOverlay() {
return this.scrollWidth;
}
document.getElementsByTagName('body')[0].style.overflowY = 'scroll';
this.OnOverlayChange.trigger(false);
}
getScrollbarWidth() {
if (this.scrollWidth == null) {
let outer = document.createElement("div");
outer.style.visibility = "hidden";
outer.style.width = "100px";
outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
document.body.appendChild(outer);
let widthNoScroll = outer.offsetWidth;
// force scrollbars
outer.style.overflow = "scroll";
// add innerdiv
let inner = document.createElement("div");
inner.style.width = "100%";
outer.appendChild(inner);
let widthWithScroll = inner.offsetWidth;
// remove divs
outer.parentNode.removeChild(outer);
this.scrollWidth = widthNoScroll - widthWithScroll;
}
return this.scrollWidth;
}
getPhantomScrollbarWidth() {
if (document.getElementsByTagName('body')[0].style.overflow == 'hidden') {
return this.getScrollbarWidth();
}
return 0;
getPhantomScrollbarWidth() {
if (document.getElementsByTagName('body')[0].style.overflow == 'hidden') {
return this.getScrollbarWidth();
}
return 0;
}
}

View File

@ -7,12 +7,12 @@ 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> >> {
return this._networkService.getJson("/autocomplete/" + text);
}
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

@ -7,134 +7,134 @@ import {GalleryService} from "../gallery.service";
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'],
providers: [AutoCompleteService, RouterLink]
selector: 'gallery-search',
templateUrl: './search.gallery.component.html',
styleUrls: ['./search.gallery.component.css'],
providers: [AutoCompleteService, RouterLink]
})
export class GallerySearchComponent {
autoCompleteItems: Array<AutoCompleteRenderItem> = [];
private searchText: string = "";
private cache = {
lastAutocomplete: "",
lastInstantSearch: ""
};
autoCompleteItems: Array<AutoCompleteRenderItem> = [];
public searchText: string = "";
private cache = {
lastAutocomplete: "",
lastInstantSearch: ""
};
SearchTypes: any = [];
SearchTypes: any = [];
constructor(private _autoCompleteService: AutoCompleteService,
private _galleryService: GalleryService,
private _route: ActivatedRoute) {
constructor(private _autoCompleteService: AutoCompleteService,
private _galleryService: GalleryService,
private _route: ActivatedRoute) {
this.SearchTypes = SearchTypes;
this.SearchTypes = SearchTypes;
this._route.params
.subscribe((params: Params) => {
let searchText = params['searchText'];
if (searchText && searchText != "") {
this.searchText = searchText;
}
});
}
onSearchChange(event: KeyboardEvent) {
let searchText = (<HTMLInputElement>event.target).value.trim();
if (Config.Client.Search.autocompleteEnabled && this.cache.lastAutocomplete != searchText) {
this.cache.lastAutocomplete = searchText;
this.autocomplete(searchText);
this._route.params
.subscribe((params: Params) => {
let searchText = params['searchText'];
if (searchText && searchText != "") {
this.searchText = searchText;
}
if (Config.Client.Search.instantSearchEnabled && this.cache.lastInstantSearch != searchText) {
this.cache.lastInstantSearch = searchText;
this._galleryService.instantSearch(searchText);
});
}
onSearchChange(event: KeyboardEvent) {
let searchText = (<HTMLInputElement>event.target).value.trim();
if (Config.Client.Search.autocompleteEnabled && this.cache.lastAutocomplete != searchText) {
this.cache.lastAutocomplete = searchText;
this.autocomplete(searchText);
}
if (Config.Client.Search.instantSearchEnabled && this.cache.lastInstantSearch != searchText) {
this.cache.lastInstantSearch = searchText;
this._galleryService.instantSearch(searchText);
}
}
public onSearch() {
if (Config.Client.Search.searchEnabled) {
this._galleryService.search(this.searchText);
}
}
public search(item: AutoCompleteItem) {
console.log("clicked");
this.searchText = item.text;
this.onSearch();
}
mouseOverAutoComplete: boolean = false;
public setMouseOverAutoComplete(value: boolean) {
this.mouseOverAutoComplete = value;
}
public onFocusLost() {
if (this.mouseOverAutoComplete == false) {
this.autoCompleteItems = [];
}
}
public onFocus() {
this.autocomplete(this.searchText);
}
private emptyAutoComplete() {
this.autoCompleteItems = [];
}
private autocomplete(searchText: string) {
if (!Config.Client.Search.autocompleteEnabled) {
return
}
if (searchText.trim().length > 0) {
this._autoCompleteService.autoComplete(searchText).then((message: Message<Array<AutoCompleteItem>>) => {
if (message.error) {
//TODO: implement
console.error(message.error);
return;
}
this.showSuggestions(message.result, searchText);
});
} else {
this.emptyAutoComplete();
}
}
public onSearch() {
if (Config.Client.Search.searchEnabled) {
this._galleryService.search(this.searchText);
}
}
private showSuggestions(suggestions: Array<AutoCompleteItem>, searchText: string) {
this.emptyAutoComplete();
suggestions.forEach((item: AutoCompleteItem) => {
let renderItem = new AutoCompleteRenderItem(item.text, searchText, item.type);
this.autoCompleteItems.push(renderItem);
});
}
public search(item: AutoCompleteItem) {
console.log("clicked");
this.searchText = item.text;
this.onSearch();
}
mouseOverAutoComplete: boolean = false;
public setMouseOverAutoComplete(value: boolean) {
this.mouseOverAutoComplete = value;
}
public onFocusLost() {
if (this.mouseOverAutoComplete == false) {
this.autoCompleteItems = [];
}
}
public onFocus() {
this.autocomplete(this.searchText);
}
private emptyAutoComplete() {
this.autoCompleteItems = [];
}
private autocomplete(searchText: string) {
if (!Config.Client.Search.autocompleteEnabled) {
return
}
if (searchText.trim().length > 0) {
this._autoCompleteService.autoComplete(searchText).then((message: Message<Array<AutoCompleteItem>>) => {
if (message.error) {
//TODO: implement
console.error(message.error);
return;
}
this.showSuggestions(message.result, searchText);
});
} else {
this.emptyAutoComplete();
}
}
private showSuggestions(suggestions: Array<AutoCompleteItem>, searchText: string) {
this.emptyAutoComplete();
suggestions.forEach((item: AutoCompleteItem) => {
let renderItem = new AutoCompleteRenderItem(item.text, searchText, item.type);
this.autoCompleteItems.push(renderItem);
});
}
public setSearchText(searchText: string) {
this.searchText = searchText;
}
public setSearchText(searchText: string) {
this.searchText = searchText;
}
}
class AutoCompleteRenderItem {
public preText: string = "";
public highLightText: string = "";
public postText: string = "";
public type: SearchTypes;
public preText: string = "";
public highLightText: string = "";
public postText: string = "";
public type: SearchTypes;
constructor(public text: string, searchText: string, type: SearchTypes) {
let preIndex = text.toLowerCase().indexOf(searchText.toLowerCase());
if (preIndex > -1) {
this.preText = text.substring(0, preIndex);
this.highLightText = text.substring(preIndex, preIndex + searchText.length);
this.postText = text.substring(preIndex + searchText.length);
} else {
this.postText = text;
}
this.type = type;
constructor(public text: string, searchText: string, type: SearchTypes) {
let preIndex = text.toLowerCase().indexOf(searchText.toLowerCase());
if (preIndex > -1) {
this.preText = text.substring(0, preIndex);
this.highLightText = text.substring(preIndex, preIndex + searchText.length);
this.postText = text.substring(preIndex + searchText.length);
} else {
this.postText = text;
}
this.type = type;
}
}

View File

@ -6,208 +6,208 @@ import {PhotoDTO} from "../../../common/entities/PhotoDTO";
import {Config} from "../../../common/config/public/Config";
export enum ThumbnailLoadingPriority{
high, medium, low
high, medium, low
}
@Injectable()
export class ThumbnailLoaderService {
que: Array<ThumbnailTask> = [];
runningRequests: number = 0;
que: Array<ThumbnailTask> = [];
runningRequests: number = 0;
constructor(private galleryChacheService: GalleryCacheService) {
constructor(private galleryChacheService: GalleryCacheService) {
}
removeTasks() {
this.que = [];
}
removeTask(taskEntry: ThumbnailTaskEntity) {
for (let i = 0; i < this.que.length; i++) {
let index = this.que[i].taskEntities.indexOf(taskEntry);
if (index == -1) {
this.que[i].taskEntities.splice(index, 1);
if (this.que[i].taskEntities.length == 0) {
this.que.splice(i, 1);
}
return;
}
}
removeTasks() {
this.que = [];
}
loadIcon(photo: IconPhoto, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
let tmp: ThumbnailTask = null;
//is image already qued?
for (let i = 0; i < this.que.length; i++) {
if (this.que[i].path == photo.getIconPath()) {
tmp = this.que[i];
break;
}
}
removeTask(taskEntry: ThumbnailTaskEntity) {
let thumbnailTaskEntity = {priority: priority, listener: listener};
//add to previous
if (tmp != null) {
tmp.taskEntities.push(thumbnailTaskEntity);
if (tmp.inProgress == true) {
listener.onStartedLoading();
}
for (let i = 0; i < this.que.length; i++) {
let index = this.que[i].taskEntities.indexOf(taskEntry);
if (index == -1) {
this.que[i].taskEntities.splice(index, 1);
if (this.que[i].taskEntities.length == 0) {
this.que.splice(i, 1);
}
return;
}
}
} else {//create new task
this.que.push(<ThumbnailTask>{
photo: photo.photo,
inProgress: false,
taskEntities: [thumbnailTaskEntity],
onLoaded: () => {
photo.iconLoaded();
},
path: photo.getIconPath()
});
}
setImmediate(this.run);
return thumbnailTaskEntity;
}
loadImage(photo: Photo, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
let tmp: ThumbnailTask = null;
//is image already qued?
for (let i = 0; i < this.que.length; i++) {
if (this.que[i].path == photo.getThumbnailPath()) {
tmp = this.que[i];
break;
}
}
loadIcon(photo: IconPhoto, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
let tmp: ThumbnailTask = null;
//is image already qued?
for (let i = 0; i < this.que.length; i++) {
if (this.que[i].path == photo.getIconPath()) {
tmp = this.que[i];
break;
}
}
let thumbnailTaskEntity = {priority: priority, listener: listener};
//add to previous
if (tmp != null) {
tmp.taskEntities.push(thumbnailTaskEntity);
if (tmp.inProgress == true) {
listener.onStartedLoading();
}
let thumbnailTaskEntity = {priority: priority, listener: listener};
//add to previous
if (tmp != null) {
tmp.taskEntities.push(thumbnailTaskEntity);
if (tmp.inProgress == true) {
listener.onStartedLoading();
}
} else {//create new task
this.que.push(<ThumbnailTask>{
photo: photo.photo,
inProgress: false,
taskEntities: [thumbnailTaskEntity],
onLoaded: () => {
photo.iconLoaded();
},
path: photo.getIconPath()
});
}
setImmediate(this.run);
return thumbnailTaskEntity;
} else {//create new task
this.que.push({
photo: photo.photo,
inProgress: false,
taskEntities: [thumbnailTaskEntity],
onLoaded: () => {
photo.thumbnailLoaded();
},
path: photo.getThumbnailPath()
});
}
setImmediate(this.run);
return thumbnailTaskEntity;
}
private getNextTask(): ThumbnailTask {
if (this.que.length === 0) {
return null;
}
loadImage(photo: Photo, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
let tmp: ThumbnailTask = null;
//is image already qued?
for (let i = 0; i < this.que.length; i++) {
if (this.que[i].path == photo.getThumbnailPath()) {
tmp = this.que[i];
break;
}
for (let i = 0; i < this.que.length; i++) {
for (let j = 0; j < this.que[i].taskEntities.length; j++) {
if (this.que[i].inProgress == false && this.que[i].taskEntities[j].priority === ThumbnailLoadingPriority.high) {
return this.que[i];
}
}
}
let thumbnailTaskEntity = {priority: priority, listener: listener};
//add to previous
if (tmp != null) {
tmp.taskEntities.push(thumbnailTaskEntity);
if (tmp.inProgress == true) {
listener.onStartedLoading();
}
} else {//create new task
this.que.push({
photo: photo.photo,
inProgress: false,
taskEntities: [thumbnailTaskEntity],
onLoaded: () => {
photo.thumbnailLoaded();
},
path: photo.getThumbnailPath()
});
for (let i = 0; i < this.que.length; i++) {
for (let j = 0; j < this.que[i].taskEntities.length; j++) {
if (this.que[i].inProgress == false && this.que[i].taskEntities[j].priority === ThumbnailLoadingPriority.medium) {
return this.que[i];
}
setImmediate(this.run);
return thumbnailTaskEntity;
}
}
private getNextTask(): ThumbnailTask {
if (this.que.length === 0) {
return null;
}
for (let i = 0; i < this.que.length; i++) {
if (this.que[i].inProgress == false) {
return this.que[i];
}
for (let i = 0; i < this.que.length; i++) {
for (let j = 0; j < this.que[i].taskEntities.length; j++) {
if (this.que[i].inProgress == false && this.que[i].taskEntities[j].priority === ThumbnailLoadingPriority.high) {
return this.que[i];
}
}
}
for (let i = 0; i < this.que.length; i++) {
for (let j = 0; j < this.que[i].taskEntities.length; j++) {
if (this.que[i].inProgress == false && this.que[i].taskEntities[j].priority === ThumbnailLoadingPriority.medium) {
return this.que[i];
}
}
}
for (let i = 0; i < this.que.length; i++) {
if (this.que[i].inProgress == false) {
return this.que[i];
}
}
return null;
}
private taskReady(task: ThumbnailTask) {
let i = this.que.indexOf(task);
if (i == -1) {
if (task.taskEntities.length !== 0) {
console.error("ThumbnailLoader: can't find task to remove");
}
return;
}
this.que.splice(i, 1);
return null;
}
private taskReady(task: ThumbnailTask) {
let i = this.que.indexOf(task);
if (i == -1) {
if (task.taskEntities.length !== 0) {
console.error("ThumbnailLoader: can't find task to remove");
}
return;
}
this.que.splice(i, 1);
}
run = () => {
if (this.que.length === 0 || this.runningRequests >= Config.Client.concurrentThumbnailGenerations) {
return;
}
let task = this.getNextTask();
if (task === null) {
return;
}
this.runningRequests++;
task.taskEntities.forEach(te => te.listener.onStartedLoading());
task.inProgress = true;
run = () => {
if (this.que.length === 0 || this.runningRequests >= Config.Client.concurrentThumbnailGenerations) {
return;
}
let task = this.getNextTask();
let curImg = new Image();
curImg.onload = () => {
task.onLoaded();
this.galleryChacheService.photoUpdated(task.photo);
task.taskEntities.forEach((te: ThumbnailTaskEntity) => te.listener.onLoad());
if (task === null) {
return;
}
this.runningRequests++;
task.taskEntities.forEach(te => te.listener.onStartedLoading());
task.inProgress = true;
let curImg = new Image();
curImg.onload = () => {
task.onLoaded();
this.galleryChacheService.photoUpdated(task.photo);
task.taskEntities.forEach((te: ThumbnailTaskEntity) => te.listener.onLoad());
this.taskReady(task);
this.runningRequests--;
this.run();
};
curImg.onerror = (error) => {
task.taskEntities.forEach((te: ThumbnailTaskEntity) => te.listener.onError(error));
this.taskReady(task);
this.runningRequests--;
this.run();
};
curImg.src = task.path;
this.taskReady(task);
this.runningRequests--;
this.run();
};
curImg.onerror = (error) => {
task.taskEntities.forEach((te: ThumbnailTaskEntity) => te.listener.onError(error));
this.taskReady(task);
this.runningRequests--;
this.run();
};
curImg.src = task.path;
};
}
export interface ThumbnailLoadingListener {
onStartedLoading: () => void;
onLoad: () => void;
onError: (error: any) => void;
onStartedLoading: () => void;
onLoad: () => void;
onError: (error: any) => void;
}
export interface ThumbnailTaskEntity {
priority: ThumbnailLoadingPriority;
listener: ThumbnailLoadingListener;
priority: ThumbnailLoadingPriority;
listener: ThumbnailLoadingListener;
}
interface ThumbnailTask {
photo: PhotoDTO;
inProgress: boolean;
taskEntities: Array<ThumbnailTaskEntity>;
path: string;
onLoaded: Function;
photo: PhotoDTO;
inProgress: boolean;
taskEntities: Array<ThumbnailTaskEntity>;
path: string;
onLoaded: Function;
}

View File

@ -4,186 +4,186 @@ import {Photo} from "./Photo";
import {IconPhoto} from "./IconPhoto";
export enum ThumbnailLoadingPriority{
high, medium, low
high, medium, low
}
@Injectable()
export class ThumbnailManagerService {
constructor(private thumbnailLoader: ThumbnailLoaderService) {
}
constructor(private thumbnailLoader: ThumbnailLoaderService) {
}
public getThumbnail(photo: Photo) {
return new Thumbnail(photo, this.thumbnailLoader);
}
public getThumbnail(photo: Photo) {
return new Thumbnail(photo, this.thumbnailLoader);
}
public getIcon(photo: IconPhoto) {
return new IconThumbnail(photo, this.thumbnailLoader);
}
public getIcon(photo: IconPhoto) {
return new IconThumbnail(photo, this.thumbnailLoader);
}
}
export abstract class ThumbnailBase {
protected available: boolean = false;
protected src: string = null;
protected loading: boolean = false;
protected onLoad: Function = null;
protected thumbnailTask: ThumbnailTaskEntity;
protected available: boolean = false;
protected src: string = null;
protected loading: boolean = false;
protected onLoad: Function = null;
protected thumbnailTask: ThumbnailTaskEntity;
constructor(protected thumbnailService: ThumbnailLoaderService) {
}
abstract set Visible(visible: boolean);
set OnLoad(onLoad: Function) {
this.onLoad = onLoad;
}
get Available() {
return this.available;
}
get Src() {
return this.src;
}
get Loading() {
return this.loading;
}
destroy() {
if (this.thumbnailTask != null) {
this.thumbnailService.removeTask(this.thumbnailTask);
this.thumbnailTask = null;
}
constructor(protected thumbnailService: ThumbnailLoaderService) {
}
abstract set Visible(visible: boolean);
set OnLoad(onLoad: Function) {
this.onLoad = onLoad;
}
get Available() {
return this.available;
}
get Src() {
return this.src;
}
get Loading() {
return this.loading;
}
destroy() {
if (this.thumbnailTask != null) {
this.thumbnailService.removeTask(this.thumbnailTask);
this.thumbnailTask = null;
}
}
}
export class IconThumbnail extends ThumbnailBase {
constructor(private photo: IconPhoto, thumbnailService: ThumbnailLoaderService) {
super(thumbnailService);
this.src = "";
if (this.photo.isIconAvailable()) {
constructor(private photo: IconPhoto, thumbnailService: ThumbnailLoaderService) {
super(thumbnailService);
this.src = "";
if (this.photo.isIconAvailable()) {
this.src = this.photo.getIconPath();
this.available = true;
if (this.onLoad) this.onLoad();
}
if (!this.photo.isIconAvailable()) {
setImmediate(() => {
let listener: ThumbnailLoadingListener = {
onStartedLoading: () => { //onLoadStarted
this.loading = true;
},
onLoad: () => {//onLoaded
this.src = this.photo.getIconPath();
this.available = true;
if (this.onLoad) this.onLoad();
}
if (!this.photo.isIconAvailable()) {
setImmediate(() => {
let listener: ThumbnailLoadingListener = {
onStartedLoading: () => { //onLoadStarted
this.loading = true;
},
onLoad: () => {//onLoaded
this.src = this.photo.getIconPath();
if (this.onLoad) this.onLoad();
this.available = true;
this.loading = false;
this.thumbnailTask = null;
},
onError: (error) => {//onError
this.thumbnailTask = null;
//TODO: handle error
//TODO: not an error if its from cache
console.error("something bad happened");
console.error(error);
}
};
this.thumbnailTask = this.thumbnailService.loadIcon(this.photo, ThumbnailLoadingPriority.high, listener);
this.available = true;
this.loading = false;
this.thumbnailTask = null;
},
onError: (error) => {//onError
this.thumbnailTask = null;
//TODO: handle error
//TODO: not an error if its from cache
console.error("something bad happened");
console.error(error);
}
};
this.thumbnailTask = this.thumbnailService.loadIcon(this.photo, ThumbnailLoadingPriority.high, listener);
});
}
});
}
set Visible(visible: boolean) {
if (!this.thumbnailTask) return;
if (visible === true) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.high;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
}
}
set Visible(visible: boolean) {
if (!this.thumbnailTask) return;
if (visible === true) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.high;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
}
}
}
export class Thumbnail extends ThumbnailBase {
constructor(private photo: Photo, thumbnailService: ThumbnailLoaderService) {
super(thumbnailService);
if (this.photo.isThumbnailAvailable()) {
constructor(private photo: Photo, thumbnailService: ThumbnailLoaderService) {
super(thumbnailService);
if (this.photo.isThumbnailAvailable()) {
this.src = this.photo.getThumbnailPath();
this.available = true;
if (this.onLoad) this.onLoad();
} else if (this.photo.isReplacementThumbnailAvailable()) {
this.src = this.photo.getReplacementThumbnailPath();
this.available = true;
}
if (!this.photo.isThumbnailAvailable()) {
setImmediate(() => {
let listener: ThumbnailLoadingListener = {
onStartedLoading: () => { //onLoadStarted
this.loading = true;
},
onLoad: () => {//onLoaded
this.src = this.photo.getThumbnailPath();
this.available = true;
if (this.onLoad) this.onLoad();
} else if (this.photo.isReplacementThumbnailAvailable()) {
this.src = this.photo.getReplacementThumbnailPath();
this.available = true;
}
if (!this.photo.isThumbnailAvailable()) {
setImmediate(() => {
let listener: ThumbnailLoadingListener = {
onStartedLoading: () => { //onLoadStarted
this.loading = true;
},
onLoad: () => {//onLoaded
this.src = this.photo.getThumbnailPath();
if (this.onLoad) this.onLoad();
this.available = true;
this.loading = false;
this.thumbnailTask = null;
},
onError: (error) => {//onError
this.thumbnailTask = null;
//TODO: handle error
//TODO: not an error if its from cache
console.error("something bad happened");
console.error(error);
}
};
if (this.photo.isReplacementThumbnailAvailable()) {
this.thumbnailTask = this.thumbnailService.loadImage(this.photo, ThumbnailLoadingPriority.medium, listener);
} else {
this.thumbnailTask = this.thumbnailService.loadImage(this.photo, ThumbnailLoadingPriority.high, listener);
}
});
}
}
set Visible(visible: boolean) {
if (!this.thumbnailTask) return;
if (visible === true) {
if (this.photo.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.high;
}
this.loading = false;
this.thumbnailTask = null;
},
onError: (error) => {//onError
this.thumbnailTask = null;
//TODO: handle error
//TODO: not an error if its from cache
console.error("something bad happened");
console.error(error);
}
};
if (this.photo.isReplacementThumbnailAvailable()) {
this.thumbnailTask = this.thumbnailService.loadImage(this.photo, ThumbnailLoadingPriority.medium, listener);
} else {
if (this.photo.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.low;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
}
this.thumbnailTask = this.thumbnailService.loadImage(this.photo, ThumbnailLoadingPriority.high, listener);
}
});
}
}
set Visible(visible: boolean) {
if (!this.thumbnailTask) return;
if (visible === true) {
if (this.photo.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.high;
}
} else {
if (this.photo.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.low;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
}
}
}
}

View File

@ -7,33 +7,33 @@ import {UserDTO} from "../../../common/entities/UserDTO";
import {ErrorCodes} from "../../../common/entities/Error";
@Component({
selector: 'login',
templateUrl: 'app/login/login.component.html',
styleUrls: ['app/login/login.component.css'],
selector: 'login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
})
export class LoginComponent implements OnInit {
loginCredential: LoginCredential;
loginError: any = null;
loginCredential: LoginCredential;
loginError: any = null;
constructor(private _authService: AuthenticationService, private _router: Router) {
this.loginCredential = new LoginCredential();
constructor(private _authService: AuthenticationService, private _router: Router) {
this.loginCredential = new LoginCredential();
}
ngOnInit() {
if (this._authService.isAuthenticated()) {
this._router.navigate(['gallery', "/"]);
}
}
ngOnInit() {
if (this._authService.isAuthenticated()) {
this._router.navigate(['gallery', "/"]);
onLogin() {
this.loginError = null;
this._authService.login(this.loginCredential).then((message: Message<UserDTO>) => {
if (message.error) {
if (message.error.code === ErrorCodes.CREDENTIAL_NOT_FOUND) {
this.loginError = "Wrong username or password";
}
}
onLogin() {
this.loginError = null;
this._authService.login(this.loginCredential).then((message: Message<UserDTO>) => {
if (message.error) {
if (message.error.code === ErrorCodes.CREDENTIAL_NOT_FOUND) {
this.loginError = "Wrong username or password";
}
}
});
}
}
});
}
}

View File

@ -1,10 +1,10 @@
export interface IRenderable {
getDimension():Dimension;
getDimension(): Dimension;
}
export interface Dimension {
top: number;
left: number;
width: number;
height: number;
top: number;
left: number;
width: number;
height: number;
}

View File

@ -7,45 +7,49 @@ import {LoginCredential} from "../../../../common/entities/LoginCredential";
import {AuthenticationService} from "./authentication.service";
class MockUserService {
public login(credential: LoginCredential) {
return Promise.resolve(new Message<UserDTO>(null, <UserDTO>{name: "testUserName"}))
}
public login(credential: LoginCredential) {
return Promise.resolve(new Message<UserDTO>(null, <UserDTO>{name: "testUserName"}))
}
public async getSessionUser() {
return null;
}
}
describe('AuthenticationService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{provide: UserService, useClass: MockUserService},
AuthenticationService]
});
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{provide: UserService, useClass: MockUserService},
AuthenticationService]
});
});
it('should call UserDTO service login', inject([AuthenticationService, UserService], (authService, userService) => {
spyOn(userService, "login").and.callThrough();
expect(userService.login).not.toHaveBeenCalled();
authService.login();
expect(userService.login).toHaveBeenCalled();
}));
it('should have NO Authenticated use', inject([AuthenticationService], (authService) => {
expect(authService.getUser()).toBe(null);
expect(authService.isAuthenticated()).toBe(false);
}));
it('should have Authenticated use', inject([AuthenticationService], (authService) => {
spyOn(authService.OnUserChanged, "trigger").and.callThrough();
authService.login();
authService.OnUserChanged.on(() => {
expect(authService.OnUserChanged.trigger).toHaveBeenCalled();
expect(authService.getUser()).not.toBe(null);
expect(authService.isAuthenticated()).toBe(true);
});
it('should call UserDTO service login', inject([AuthenticationService, UserService], (authService, userService) => {
spyOn(userService, "login").and.callThrough();
expect(userService.login).not.toHaveBeenCalled();
authService.login();
expect(userService.login).toHaveBeenCalled();
}));
it('should have NO Authenticated use', inject([AuthenticationService], (authService) => {
expect(authService.getUser()).toBe(null);
expect(authService.isAuthenticated()).toBe(false);
}));
it('should have Authenticated use', inject([AuthenticationService], (authService) => {
spyOn(authService.OnUserChanged, "trigger").and.callThrough();
authService.login();
authService.OnUserChanged.on(() => {
expect(authService.OnUserChanged.trigger).toHaveBeenCalled();
expect(authService.getUser()).not.toBe(null);
expect(authService.isAuthenticated()).toBe(true);
});
}));
}));
});

View File

@ -9,76 +9,76 @@ import {ErrorCodes} from "../../../../common/entities/Error";
import {Config} from "../../../../common/config/public/Config";
declare module ServerInject {
export let user: UserDTO;
export let user: UserDTO;
}
@Injectable()
export class AuthenticationService {
private _user: UserDTO = null;
public OnUserChanged: Event<UserDTO>;
private _user: UserDTO = null;
public OnUserChanged: Event<UserDTO>;
constructor(private _userService: UserService) {
this.OnUserChanged = new Event();
//picking up session..
if (this.isAuthenticated() == false && Cookie.get('pigallery2-session') != null) {
if (typeof ServerInject !== "undefined" && typeof ServerInject.user !== "undefined") {
this.setUser(ServerInject.user);
}
this.getSessionUser();
} else {
this.OnUserChanged.trigger(this._user);
}
constructor(private _userService: UserService) {
this.OnUserChanged = new Event();
//picking up session..
if (this.isAuthenticated() == false && Cookie.get('pigallery2-session') != null) {
if (typeof ServerInject !== "undefined" && typeof ServerInject.user !== "undefined") {
this.setUser(ServerInject.user);
}
this.getSessionUser();
} else {
this.OnUserChanged.trigger(this._user);
}
private getSessionUser() {
this._userService.getSessionUser().then((message: Message<UserDTO>) => {
if (message.error) {
console.log(message.error);
} else {
this._user = message.result;
this.OnUserChanged.trigger(this._user);
}
});
}
}
private setUser(user: UserDTO) {
this._user = user;
private getSessionUser() {
this._userService.getSessionUser().then((message: Message<UserDTO>) => {
if (message.error) {
console.log(message.error);
} else {
this._user = message.result;
this.OnUserChanged.trigger(this._user);
}
}
});
}
public login(credential: LoginCredential) {
return this._userService.login(credential).then((message: Message<UserDTO>) => {
if (message.error) {
console.log(ErrorCodes[message.error.code] + ", message: ", message.error.message);
} else {
this.setUser(message.result);
}
return message;
});
}
private setUser(user: UserDTO) {
this._user = user;
this.OnUserChanged.trigger(this._user);
}
public login(credential: LoginCredential) {
return this._userService.login(credential).then((message: Message<UserDTO>) => {
if (message.error) {
console.log(ErrorCodes[message.error.code] + ", message: ", message.error.message);
} else {
this.setUser(message.result);
}
return message;
});
}
public isAuthenticated(): boolean {
if (Config.Client.authenticationRequired === false) {
return true;
}
return !!(this._user && this._user != null);
public isAuthenticated(): boolean {
if (Config.Client.authenticationRequired === false) {
return true;
}
return !!(this._user && this._user != null);
}
public getUser() {
if (Config.Client.authenticationRequired === false) {
return <UserDTO>{name: "", password: "", role: UserRoles.Admin};
}
return this._user;
public getUser() {
if (Config.Client.authenticationRequired === false) {
return <UserDTO>{name: "", password: "", role: UserRoles.Admin};
}
return this._user;
}
public logout() {
this._userService.logout();
this.setUser(null);
}
public logout() {
this._userService.logout();
this.setUser(null);
}
}

View File

@ -7,169 +7,169 @@ import {Message} from "../../../../common/entities/Message";
describe('NetworkService Success tests', () => {
let connection: MockConnection = null;
let connection: MockConnection = null;
let testUrl = "/test/url";
let testData = {data: "testData"};
let testResponse = "testResponse";
let testResponseMessage = new Message(null, testResponse);
let testUrl = "/test/url";
let testData = {data: "testData"};
let testResponse = "testResponse";
let testResponseMessage = new Message(null, testResponse);
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
}, deps: [MockBackend, BaseRequestOptions]
},
NetworkService
]
});
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
}, deps: [MockBackend, BaseRequestOptions]
},
NetworkService
]
});
});
beforeEach(inject([MockBackend], (backend) => {
backend.connections.subscribe((c) => {
connection = c;
connection.mockRespond(new Response(
new ResponseOptions(
{
body: testResponseMessage
}
)));
});
}));
afterEach(() => {
expect(connection.request.url).toBe("/api" + testUrl);
});
it('should call GET', inject([NetworkService], (networkService) => {
networkService.getJson(testUrl).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
}));
beforeEach(inject([MockBackend], (backend) => {
it('should call POST', inject([NetworkService, MockBackend], (networkService) => {
backend.connections.subscribe((c) => {
connection = c;
connection.mockRespond(new Response(
new ResponseOptions(
{
body: testResponseMessage
}
)));
});
}));
networkService.postJson(testUrl, testData).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
expect(connection.request.text()).toBe(JSON.stringify(testData));
afterEach(() => {
expect(connection.request.url).toBe("/api" + testUrl);
networkService.postJson(testUrl).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
expect(connection.request.text()).toBe(JSON.stringify({}));
}));
it('should call PUT', inject([NetworkService, MockBackend], (networkService) => {
networkService.putJson(testUrl, testData).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
it('should call GET', inject([NetworkService], (networkService) => {
networkService.getJson(testUrl).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
}));
it('should call POST', inject([NetworkService, MockBackend], (networkService) => {
networkService.postJson(testUrl, testData).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
expect(connection.request.text()).toBe(JSON.stringify(testData));
expect(connection.request.text()).toBe(JSON.stringify(testData));
networkService.postJson(testUrl).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
expect(connection.request.text()).toBe(JSON.stringify({}));
}));
networkService.putJson(testUrl).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
expect(connection.request.text()).toBe(JSON.stringify({}));
it('should call PUT', inject([NetworkService, MockBackend], (networkService) => {
}));
networkService.putJson(testUrl, testData).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
it('should call DELETE', inject([NetworkService, MockBackend], (networkService) => {
expect(connection.request.text()).toBe(JSON.stringify(testData));
networkService.putJson(testUrl).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
expect(connection.request.text()).toBe(JSON.stringify({}));
}));
it('should call DELETE', inject([NetworkService, MockBackend], (networkService) => {
networkService.deleteJson(testUrl).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
}));
networkService.deleteJson(testUrl).then((res: Message<any>) => {
expect(res.result).toBe(testResponse);
});
}));
});
describe('NetworkService Fail tests', () => {
let connection: MockConnection = null;
let connection: MockConnection = null;
let testUrl = "/test/url";
let testData = {data: "testData"};
let testError = "testError";
let testUrl = "/test/url";
let testData = {data: "testData"};
let testError = "testError";
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
}, deps: [MockBackend, BaseRequestOptions]
},
NetworkService
]
});
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
}, deps: [MockBackend, BaseRequestOptions]
},
NetworkService
]
});
});
beforeEach(inject([MockBackend], (backend) => {
backend.connections.subscribe((c) => {
connection = c;
connection.mockError({name: "errorName", message: testError});
});
}));
afterEach(() => {
expect(connection.request.url).toBe("/api" + testUrl);
});
it('should call GET with error', inject([NetworkService], (networkService) => {
networkService.getJson(testUrl).then((res: Message<any>) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
});
beforeEach(inject([MockBackend], (backend) => {
}));
backend.connections.subscribe((c) => {
connection = c;
connection.mockError({name: "errorName", message: testError});
it('should call POST with error', inject([NetworkService, MockBackend], (networkService) => {
});
}));
networkService.postJson(testUrl, testData).then((res: Message<any>) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
});
expect(connection.request.text()).toBe(JSON.stringify(testData));
}));
afterEach(() => {
it('should call PUT with error', inject([NetworkService, MockBackend], (networkService) => {
expect(connection.request.url).toBe("/api" + testUrl);
networkService.putJson(testUrl, testData).then((res: Message<any>) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
});
it('should call GET with error', inject([NetworkService], (networkService) => {
expect(connection.request.text()).toBe(JSON.stringify(testData));
networkService.getJson(testUrl).then((res: Message<any>) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
});
}));
}));
it('should call DELETE with error', inject([NetworkService, MockBackend], (networkService) => {
it('should call POST with error', inject([NetworkService, MockBackend], (networkService) => {
networkService.postJson(testUrl, testData).then((res: Message<any>) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
});
expect(connection.request.text()).toBe(JSON.stringify(testData));
}));
it('should call PUT with error', inject([NetworkService, MockBackend], (networkService) => {
networkService.putJson(testUrl, testData).then((res: Message<any>) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
});
expect(connection.request.text()).toBe(JSON.stringify(testData));
}));
it('should call DELETE with error', inject([NetworkService, MockBackend], (networkService) => {
networkService.deleteJson(testUrl).then((res: Message<any>) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
});
}));
networkService.deleteJson(testUrl).then((res: Message<any>) => {
expect(res).toBe(null);
}).catch((err) => {
expect(err).toBe(testError);
});
}));
});

View File

@ -1,55 +1,55 @@
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";
@Injectable()
export class NetworkService {
_baseUrl = "/api";
_baseUrl = "/api";
constructor(protected _http:Http) {
constructor(protected _http: Http) {
}
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});
if (method == "get" || method == "delete") {
return <any>this._http[method](this._baseUrl + url, options)
.toPromise()
.then(res => <Message<any>> res.json())
.catch(NetworkService.handleError);
}
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});
return this._http[method](this._baseUrl + url, body, options)
.toPromise()
.then((res: any) => <Message<any>> res.json())
.catch(NetworkService.handleError);
}
if (method == "get" || method == "delete") {
return <any>this._http[method](this._baseUrl + url, options)
.toPromise()
.then(res => <Message<any>> res.json())
.catch(NetworkService.handleError);
}
public postJson<T>(url: string, data: any = {}): Promise<T> {
return this.callJson("post", url, data);
}
return this._http[method](this._baseUrl + url, body, options)
.toPromise()
.then((res: any) => <Message<any>> res.json())
.catch(NetworkService.handleError);
}
public putJson<T>(url: string, data: any = {}): Promise<T> {
return this.callJson("put", url, data);
}
public postJson<T>(url:string, data:any = {}):Promise<T> {
return this.callJson("post", url, data);
}
public putJson<T>(url:string, data:any = {}):Promise<T> {
return this.callJson("put", url, data);
}
public getJson<T>(url:string):Promise<T> {
return this.callJson("get", url);
}
public getJson<T>(url: string): Promise<T> {
return this.callJson("get", url);
}
public deleteJson<T>(url:string):Promise<T> {
return this.callJson("delete", url);
}
public deleteJson<T>(url: string): Promise<T> {
return this.callJson("delete", url);
}
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);
return Promise.reject(error.message || error.json().error || 'Server error');
}
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);
return Promise.reject(error.message || error.json().error || 'Server error');
}
}

View File

@ -9,36 +9,36 @@ import {LoginCredential} from "../../../../common/entities/LoginCredential";
describe('UserService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
}, deps: [MockBackend, BaseRequestOptions]
},
NetworkService,
UserService]
});
it('should call postJson at login', inject([UserService, NetworkService], (userService, networkService) => {
spyOn(networkService, "postJson");
let credential = new LoginCredential("name", "pass");
userService.login(credential);
expect(networkService.postJson).toHaveBeenCalled();
expect(networkService.postJson.calls.argsFor(0)).toEqual(["/user/login", {"loginCredential": credential}]);
}));
it('should call getJson at getSessionUser', inject([UserService, NetworkService], (userService, networkService) => {
spyOn(networkService, "getJson");
userService.getSessionUser();
expect(networkService.getJson).toHaveBeenCalled();
expect(networkService.getJson.calls.argsFor(0)).toEqual(["/user/login"]);
}));
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
}, deps: [MockBackend, BaseRequestOptions]
},
NetworkService,
UserService]
});
});
it('should call postJson at login', inject([UserService, NetworkService], (userService, networkService) => {
spyOn(networkService, "postJson");
let credential = new LoginCredential("name", "pass");
userService.login(credential);
expect(networkService.postJson).toHaveBeenCalled();
expect(networkService.postJson.calls.argsFor(0)).toEqual(["/user/login", {"loginCredential": credential}]);
}));
it('should call getJson at getSessionUser', inject([UserService, NetworkService], (userService, networkService) => {
spyOn(networkService, "getJson");
userService.getSessionUser();
expect(networkService.getJson).toHaveBeenCalled();
expect(networkService.getJson.calls.argsFor(0)).toEqual(["/user/login"]);
}));
});
});

View File

@ -8,20 +8,20 @@ import {Message} from "../../../../common/entities/Message";
export class UserService {
constructor(private _networkService: NetworkService) {
}
constructor(private _networkService: NetworkService) {
}
public logout(): Promise<Message<string>> {
console.log("call logout");
return this._networkService.postJson("/user/logout");
}
public logout(): Promise<Message<string>> {
console.log("call logout");
return this._networkService.postJson("/user/logout");
}
public login(credential: LoginCredential): Promise<Message<UserDTO>> {
return this._networkService.postJson("/user/login", {"loginCredential": credential});
}
public login(credential: LoginCredential): Promise<Message<UserDTO>> {
return this._networkService.postJson("/user/login", {"loginCredential": credential});
}
public getSessionUser(): Promise<Message<UserDTO>> {
return this._networkService.getJson("/user/login");
}
public getSessionUser(): Promise<Message<UserDTO>> {
return this._networkService.getJson("/user/login");
}
}

View File

@ -4,24 +4,24 @@ import {Injectable} from "@angular/core";
export class NotificationService {
constructor() {
constructor() {
}
}
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,8 +4,8 @@ import {UserRoles} from "../../../common/entities/UserDTO";
@Pipe({name: 'stringifyRole'})
export class StringifyRole implements PipeTransform {
transform(role:string):number {
return UserRoles[role];
}
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

@ -7,66 +7,66 @@ import {Message} from "../../../../common/entities/Message";
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'],
providers: [UserManagerSettingsService],
selector: 'settings-usermanager',
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) {
constructor(private _authService: AuthenticationService, private _router: Router, private _userSettings: UserManagerSettingsService) {
}
ngOnInit() {
if (!this._authService.isAuthenticated() || this._authService.getUser().role < UserRoles.Admin) {
this._router.navigate(['login']);
return;
}
this.userRoles = Utils.enumToArray(UserRoles).filter(r => r.key <= this._authService.getUser().role);
this.getUsersList();
}
private getUsersList() {
this._userSettings.getUsers().then((result: Message<Array<UserDTO>>) => {
this.users = result.result;
});
}
canModifyUser(user: UserDTO): boolean {
let currentUser = this._authService.getUser();
if (!currentUser) {
return false;
}
ngOnInit() {
if (!this._authService.isAuthenticated() || this._authService.getUser().role < UserRoles.Admin) {
this._router.navigate(['login']);
return;
}
this.userRoles = Utils.enumToArray(UserRoles).filter(r => r.key <= this._authService.getUser().role);
this.getUsersList();
}
return currentUser.name != user.name && currentUser.role >= user.role;
}
private getUsersList() {
this._userSettings.getUsers().then((result: Message<Array<UserDTO>>) => {
this.users = result.result;
});
}
initNewUser() {
this.newUser = <UserDTO>{role: UserRoles.User};
}
addNewUser() {
this._userSettings.createUser(this.newUser).then(() => {
this.getUsersList();
});
}
canModifyUser(user: UserDTO): boolean {
let currentUser = this._authService.getUser();
if (!currentUser) {
return false;
}
updateRole(user: UserDTO) {
this._userSettings.updateRole(user).then(() => {
this.getUsersList();
});
}
return currentUser.name != user.name && currentUser.role >= user.role;
}
initNewUser() {
this.newUser = <UserDTO>{role: UserRoles.User};
}
addNewUser() {
this._userSettings.createUser(this.newUser).then(() => {
this.getUsersList();
});
}
updateRole(user: UserDTO) {
this._userSettings.updateRole(user).then(() => {
this.getUsersList();
});
}
deleteUser(user: UserDTO) {
this._userSettings.deleteUser(user).then(() => {
this.getUsersList();
});
}
deleteUser(user: UserDTO) {
this._userSettings.deleteUser(user).then(() => {
this.getUsersList();
});
}
}

View File

@ -7,24 +7,24 @@ import {Message} from "../../../../common/entities/Message";
export class UserManagerSettingsService {
constructor(private _networkService:NetworkService) {
}
constructor(private _networkService: NetworkService) {
}
public createUser(user: UserDTO): Promise<Message<string>> {
return this._networkService.putJson("/user", {newUser: user});
}
public createUser(user: UserDTO): Promise<Message<string>> {
return this._networkService.putJson("/user", {newUser: user});
}
public getUsers(): Promise<Message<Array<UserDTO>>> {
return this._networkService.getJson("/user/list");
}
public getUsers(): Promise<Message<Array<UserDTO>>> {
return this._networkService.getJson("/user/list");
}
public deleteUser(user: UserDTO) {
return this._networkService.deleteJson("/user/" + user.id);
}
public deleteUser(user: UserDTO) {
return this._networkService.deleteJson("/user/" + user.id);
}
public updateRole(user: UserDTO) {
return this._networkService.postJson("/user/" + user.id + "/role", {newRole: user.role});
}
public updateRole(user: UserDTO) {
return this._networkService.postJson("/user/" + user.id + "/role", {newRole: user.role});
}
}

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) {
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
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
config.set({
basePath: '',
frameworks: ['jasmine'],
plugins: [
require('karma-jasmine'),
require('karma-phantomjs-launcher'),
require('karma-jasmine-html-reporter')
],
client: {
builtPaths: [appBase, commonBase, testingBase], // add more spec base paths as needed
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
},
exclude: [],
preprocessors: {},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['PhantomJS'],
singleRun: false
})
}
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-phantomjs-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
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 } }));
}
};

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