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:
parent
e7cb6311a9
commit
8b9f287a88
58
.angular-cli.json
Normal file
58
.angular-cli.json
Normal 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
13
.editorconfig
Normal 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
9
.gitignore
vendored
@ -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/
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
192
backend/model/DiskMangerTask.ts
Normal file
192
backend/model/DiskMangerTask.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
10
backend/model/exif.d.ts
vendored
10
backend/model/exif.d.ts
vendored
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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'));
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -1,14 +1,14 @@
|
||||
export var MessageTypes = {
|
||||
Client: {
|
||||
Login: {
|
||||
Authenticate: "Authenticate"
|
||||
}
|
||||
|
||||
},
|
||||
Server: {
|
||||
Login: {
|
||||
Authenticated: "Authenticated"
|
||||
}
|
||||
|
||||
Client: {
|
||||
Login: {
|
||||
Authenticate: "Authenticate"
|
||||
}
|
||||
};
|
||||
|
||||
},
|
||||
Server: {
|
||||
Login: {
|
||||
Authenticated: "Authenticated"
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
176
common/Utils.ts
176
common/Utils.ts
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,4 +26,5 @@ export interface ServerConfig {
|
||||
imagesFolder: string;
|
||||
thumbnail: ThumbnailConfig;
|
||||
database: DataBaseConfig;
|
||||
enableThreading:boolean;
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ export class PrivateConfigClass extends PublicConfigClass {
|
||||
database: "pigallery2"
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
enableThreading: true
|
||||
};
|
||||
|
||||
public setDatabaseType(type: DatabaseType) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export class LoginCredential {
|
||||
constructor(public username:string = "", public password:string = "") {
|
||||
constructor(public username: string = "", public password: string = "") {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export class UserModificationRequest {
|
||||
constructor(public id:number) {
|
||||
}
|
||||
constructor(public id: number) {
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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"]);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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">
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
}));
|
||||
}));
|
||||
|
||||
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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"]);
|
||||
}));
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,8 @@
|
||||
aria-hidden="true">×</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>
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
4
frontend/config_inject.ejs
Normal file
4
frontend/config_inject.ejs
Normal file
@ -0,0 +1,4 @@
|
||||
var ServerInject = {
|
||||
user: <%- JSON.stringify(user); %>,
|
||||
ConfigInject: <%- JSON.stringify(clientConfig); %>
|
||||
}
|
3
frontend/environments/environment.prod.ts
Normal file
3
frontend/environments/environment.prod.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
8
frontend/environments/environment.ts
Normal file
8
frontend/environments/environment.ts
Normal 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
|
||||
};
|
@ -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
28
frontend/index.html
Normal 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
61
frontend/polyfills.ts
Normal 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
1
frontend/styles.css
Normal file
@ -0,0 +1 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Add barrels and stuff
|
||||
* Adjust as necessary for your application needs.
|
||||
*/
|
||||
// (function (global) {
|
||||
// System.config({
|
||||
// packages: {
|
||||
// // add packages here
|
||||
// }
|
||||
// });
|
||||
// })(this);
|
@ -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
30
frontend/test.ts
Normal 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();
|
13
frontend/tsconfig.app.json
Normal file
13
frontend/tsconfig.app.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"module": "es2015",
|
||||
"baseUrl": "",
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
20
frontend/tsconfig.spec.json
Normal file
20
frontend/tsconfig.spec.json
Normal 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
5
frontend/typings.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/* SystemJS module definition */
|
||||
declare var module: NodeModule;
|
||||
interface NodeModule {
|
||||
id: string;
|
||||
}
|
@ -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);
|
||||
}
|
137
karma.conf.js
137
karma.conf.js
@ -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
|
||||
});
|
||||
};
|
||||
|
19
package.json
19
package.json
@ -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
28
protractor.conf.js
Normal 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
Loading…
Reference in New Issue
Block a user