diff --git a/.angular-cli.json b/.angular-cli.json new file mode 100644 index 0000000..2eb7b03 --- /dev/null +++ b/.angular-cli.json @@ -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": {} + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6e87a00 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitignore b/.gitignore index cd08719..c9c4a71 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/backend/Logger.ts b/backend/Logger.ts index 9ff9d08..ac64ab9 100644 --- a/backend/Logger.ts +++ b/backend/Logger.ts @@ -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); \ No newline at end of file +export const Logger = new winston.Logger(winstonSettings); diff --git a/backend/ProjectPath.ts b/backend/ProjectPath.ts index f488678..2b48da4 100644 --- a/backend/ProjectPath.ts +++ b/backend/ProjectPath.ts @@ -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(); \ No newline at end of file +export const ProjectPath = new ProjectPathClass(); diff --git a/backend/middlewares/GalleryMWs.ts b/backend/middlewares/GalleryMWs.ts index a069d3a..1f204c6 100644 --- a/backend/middlewares/GalleryMWs.ts +++ b/backend/middlewares/GalleryMWs.ts @@ -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) => { + 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) => { - if (err || !items) { - return next(new Error(ErrorCodes.GENERAL_ERROR, err)); - } - req.resultPipe = items; - return next(); - }); - } - - -} \ No newline at end of file +} diff --git a/backend/middlewares/RenderingMWs.ts b/backend/middlewares/RenderingMWs.ts index c4dbbb0..368b734 100644 --- a/backend/middlewares/RenderingMWs.ts +++ b/backend/middlewares/RenderingMWs.ts @@ -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(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(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(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(err, null); + return res.json(message); } + return next(err); + } - protected static renderMessage(res:Response, content:T) { - let message = new Message(null, content); - res.json(message); - } + protected static renderMessage(res: Response, content: T) { + let message = new Message(null, content); + res.json(message); + } -} \ No newline at end of file +} diff --git a/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts b/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts index 33f911b..129b677 100644 --- a/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts +++ b/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts @@ -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 = { 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 { diff --git a/backend/model/DiskManger.ts b/backend/model/DiskManger.ts index 3f1fcec..665e4a6 100644 --- a/backend/model/DiskManger.ts +++ b/backend/model/DiskManger.ts @@ -1,228 +1,64 @@ /// 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 => { - return new Promise((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 = { - 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 => { - return new Promise((resolve, reject) => { - let promises: Array> = []; - let directory = { - 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({ - 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 = { + 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); + } } + } } diff --git a/backend/model/DiskMangerTask.ts b/backend/model/DiskMangerTask.ts new file mode 100644 index 0000000..7e4f17c --- /dev/null +++ b/backend/model/DiskMangerTask.ts @@ -0,0 +1,192 @@ +/// +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 => { + return new Promise((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 = { + 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 => { + return new Promise((resolve, reject) => { + let promises: Array> = []; + let directory = { + 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({ + 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; + } + +} diff --git a/backend/model/ObjectManagerRepository.ts b/backend/model/ObjectManagerRepository.ts index 2d970a4..c0fdf0b 100644 --- a/backend/model/ObjectManagerRepository.ts +++ b/backend/model/ObjectManagerRepository.ts @@ -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 { + return new Promise((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 { - return new Promise((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; + } -} \ No newline at end of file +} diff --git a/backend/model/exif.d.ts b/backend/model/exif.d.ts index 2c47310..2ae20ca 100644 --- a/backend/model/exif.d.ts +++ b/backend/model/exif.d.ts @@ -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; } diff --git a/backend/routes/AdminRouter.ts b/backend/routes/AdminRouter.ts index 72bcb8a..424296b 100644 --- a/backend/routes/AdminRouter.ts +++ b/backend/routes/AdminRouter.ts @@ -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 + ); + }; -} \ No newline at end of file +} diff --git a/backend/routes/ErrorRouter.ts b/backend/routes/ErrorRouter.ts index 6389fb6..9a43727 100644 --- a/backend/routes/ErrorRouter.ts +++ b/backend/routes/ErrorRouter.ts @@ -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 + ); + } -} \ No newline at end of file +} diff --git a/backend/routes/GalleryRouter.ts b/backend/routes/GalleryRouter.ts index 5df7358..623fcb2 100644 --- a/backend/routes/GalleryRouter.ts +++ b/backend/routes/GalleryRouter.ts @@ -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 + ); + }; -} \ No newline at end of file +} diff --git a/backend/routes/PublicRouter.ts b/backend/routes/PublicRouter.ts index 843073e..6c7bf5c 100644 --- a/backend/routes/PublicRouter.ts +++ b/backend/routes/PublicRouter.ts @@ -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')); + }; - } + } -} \ No newline at end of file +} diff --git a/backend/routes/SharingRouter.ts b/backend/routes/SharingRouter.ts index 67f480e..adb7954 100644 --- a/backend/routes/SharingRouter.ts +++ b/backend/routes/SharingRouter.ts @@ -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 + ); + }; -} \ No newline at end of file +} diff --git a/backend/routes/UserRouter.ts b/backend/routes/UserRouter.ts index e4b0151..7e24709 100644 --- a/backend/routes/UserRouter.ts +++ b/backend/routes/UserRouter.ts @@ -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 + ); + }; -} \ No newline at end of file +} diff --git a/backend/server.ts b/backend/server.ts index 73cae7e..be8a5f2 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -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(); \ No newline at end of file +new Server(); diff --git a/common/MessageTypes.ts b/common/MessageTypes.ts index 0760c8c..e66961e 100644 --- a/common/MessageTypes.ts +++ b/common/MessageTypes.ts @@ -1,14 +1,14 @@ export var MessageTypes = { - Client: { - Login: { - Authenticate: "Authenticate" - } - - }, - Server: { - Login: { - Authenticated: "Authenticated" - } - + Client: { + Login: { + Authenticate: "Authenticate" } -}; \ No newline at end of file + + }, + Server: { + Login: { + Authenticated: "Authenticated" + } + + } +}; diff --git a/common/Utils.ts b/common/Utils.ts index 2cce111..91d8a6a 100644 --- a/common/Utils.ts +++ b/common/Utils.ts @@ -1,107 +1,107 @@ export class Utils { - static clone(object: T): T { - return JSON.parse(JSON.stringify(object)); + static clone(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) { + 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) { - 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) { - 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) { - - 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; + } } diff --git a/common/config/private/IPrivateConfig.ts b/common/config/private/IPrivateConfig.ts index 66f5ba2..c9d12fa 100644 --- a/common/config/private/IPrivateConfig.ts +++ b/common/config/private/IPrivateConfig.ts @@ -26,4 +26,5 @@ export interface ServerConfig { imagesFolder: string; thumbnail: ThumbnailConfig; database: DataBaseConfig; + enableThreading:boolean; } diff --git a/common/config/private/PrivateConfigClass.ts b/common/config/private/PrivateConfigClass.ts index 7a43e0c..84fd30a 100644 --- a/common/config/private/PrivateConfigClass.ts +++ b/common/config/private/PrivateConfigClass.ts @@ -24,7 +24,8 @@ export class PrivateConfigClass extends PublicConfigClass { database: "pigallery2" } - } + }, + enableThreading: true }; public setDatabaseType(type: DatabaseType) { diff --git a/common/entities/AutoCompleteItem.ts b/common/entities/AutoCompleteItem.ts index 4dc165b..5bea844 100644 --- a/common/entities/AutoCompleteItem.ts +++ b/common/entities/AutoCompleteItem.ts @@ -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; + } } diff --git a/common/entities/ConentWrapper.ts b/common/entities/ConentWrapper.ts index 09e82e5..3c59883 100644 --- a/common/entities/ConentWrapper.ts +++ b/common/entities/ConentWrapper.ts @@ -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; + } -} \ No newline at end of file +} diff --git a/common/entities/DirectoryDTO.ts b/common/entities/DirectoryDTO.ts index 51b8543..1fd41d3 100644 --- a/common/entities/DirectoryDTO.ts +++ b/common/entities/DirectoryDTO.ts @@ -1,11 +1,11 @@ import {PhotoDTO} from "./PhotoDTO"; export interface DirectoryDTO { - id: number; - name: string; - path: string; - lastUpdate: number; - parent: DirectoryDTO; - directories: Array; - photos: Array; -} \ No newline at end of file + id: number; + name: string; + path: string; + lastUpdate: number; + parent: DirectoryDTO; + directories: Array; + photos: Array; +} diff --git a/common/entities/Error.ts b/common/entities/Error.ts index 49cfed1..5ffe6d0 100644 --- a/common/entities/Error.ts +++ b/common/entities/Error.ts @@ -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) { - } -} \ No newline at end of file + constructor(public code: ErrorCodes, public message?: string) { + } +} diff --git a/common/entities/LoginCredential.ts b/common/entities/LoginCredential.ts index bc7a579..41375b3 100644 --- a/common/entities/LoginCredential.ts +++ b/common/entities/LoginCredential.ts @@ -1,5 +1,5 @@ export class LoginCredential { - constructor(public username:string = "", public password:string = "") { + constructor(public username: string = "", public password: string = "") { - } -} \ No newline at end of file + } +} diff --git a/common/entities/Message.ts b/common/entities/Message.ts index 56177fb..4618fa0 100644 --- a/common/entities/Message.ts +++ b/common/entities/Message.ts @@ -1,11 +1,11 @@ import {Error} from "./Error"; export class Message { - 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; - } -} \ No newline at end of file + constructor(error: Error, result: T) { + this.error = error; + this.result = result; + } +} diff --git a/common/entities/PasswordChangeRequest.ts b/common/entities/PasswordChangeRequest.ts index dfaa0bf..b2b0202 100644 --- a/common/entities/PasswordChangeRequest.ts +++ b/common/entities/PasswordChangeRequest.ts @@ -2,7 +2,7 @@ import {UserModificationRequest} from "./UserModificationRequest"; export class PasswordChangeRequest extends UserModificationRequest { - constructor(id:number, public oldPassword:string, public newPassword:string) { - super(id); - } -} \ No newline at end of file + constructor(id: number, public oldPassword: string, public newPassword: string) { + super(id); + } +} diff --git a/common/entities/PhotoDTO.ts b/common/entities/PhotoDTO.ts index 9fe07da..aee529c 100644 --- a/common/entities/PhotoDTO.ts +++ b/common/entities/PhotoDTO.ts @@ -1,47 +1,47 @@ import {DirectoryDTO} from "./DirectoryDTO"; export interface PhotoDTO { - id: number; - name: string; - directory: DirectoryDTO; - metadata: PhotoMetadata; - readyThumbnails: Array; - readyIcon: boolean; + id: number; + name: string; + directory: DirectoryDTO; + metadata: PhotoMetadata; + readyThumbnails: Array; + readyIcon: boolean; } export interface PhotoMetadata { - keywords: Array; - cameraData: CameraMetadata; - positionData: PositionMetaData; - size: ImageSize; - creationDate: number; + keywords: Array; + 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; -} \ No newline at end of file +} diff --git a/common/entities/SearchResult.ts b/common/entities/SearchResult.ts index 0afe377..2d88c6a 100644 --- a/common/entities/SearchResult.ts +++ b/common/entities/SearchResult.ts @@ -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; - photos: Array; -} \ No newline at end of file + searchText: string; + searchType: SearchTypes; + directories: Array; + photos: Array; +} diff --git a/common/entities/UserDTO.ts b/common/entities/UserDTO.ts index f886b3c..1397aff 100644 --- a/common/entities/UserDTO.ts +++ b/common/entities/UserDTO.ts @@ -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; -} \ No newline at end of file + id: number; + name: string; + password: string; + role: UserRoles; +} diff --git a/common/entities/UserModificationRequest.ts b/common/entities/UserModificationRequest.ts index 0f51f2e..3148c2f 100644 --- a/common/entities/UserModificationRequest.ts +++ b/common/entities/UserModificationRequest.ts @@ -1,4 +1,4 @@ export class UserModificationRequest { - constructor(public id:number) { - } + constructor(public id: number) { + } } diff --git a/common/event/Event.ts b/common/event/Event.ts index 5c4faa9..0332712 100644 --- a/common/event/Event.ts +++ b/common/event/Event.ts @@ -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 { - 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)); } + } } diff --git a/common/event/Event2Args.ts b/common/event/Event2Args.ts index 9e22e76..33f9b5d 100644 --- a/common/event/Event2Args.ts +++ b/common/event/Event2Args.ts @@ -1,21 +1,21 @@ -export class Event2Args { - private handlers: { (data?: T,data2?: M): void; }[] = []; +export class Event2Args { + 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)); } + } } diff --git a/common/event/EventLimit.ts b/common/event/EventLimit.ts index 15199f7..9a267d0 100644 --- a/common/event/EventLimit.ts +++ b/common/event/EventLimit.ts @@ -1,70 +1,74 @@ export class EventLimit { - private lastTriggerValue:T = null; + private lastTriggerValue: T = null; - private handlers:Array> = []; + private handlers: Array> = []; - 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 { - 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 extends EventLimitHandler{ - 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 extends EventLimitHandler { + 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; + } } diff --git a/frontend/app/admin/admin.component.ts b/frontend/app/admin/admin.component.ts index aa15e22..ba45835 100644 --- a/frontend/app/admin/admin.component.ts +++ b/frontend/app/admin/admin.component.ts @@ -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; } + } } diff --git a/frontend/app/app.component.ts b/frontend/app/app.component.ts index cbdb2ce..522f4bc 100644 --- a/frontend/app/app.component.ts +++ b/frontend/app/app.component.ts @@ -4,31 +4,31 @@ import {UserDTO} from "../../common/entities/UserDTO"; import {Router} from "@angular/router"; @Component({ - selector: 'pi-gallery2-app', - template: ``, + selector: 'pi-gallery2-app', + template: ``, }) 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"]); + } + } - }); + }); - } -} \ No newline at end of file + } +} diff --git a/frontend/app/app.module.ts b/frontend/app/app.module.ts index 36afba9..b696f43 100644 --- a/frontend/app/app.module.ts +++ b/frontend/app/app.module.ts @@ -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 { -} \ No newline at end of file +} diff --git a/frontend/app/app.routing.ts b/frontend/app/app.routing.ts index 45d6790..f45df79 100644 --- a/frontend/app/app.routing.ts +++ b/frontend/app/app.routing.ts @@ -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); diff --git a/frontend/app/frame/frame.component.html b/frontend/app/frame/frame.component.html index e582e70..f985843 100644 --- a/frontend/app/frame/frame.component.html +++ b/frontend/app/frame/frame.component.html @@ -8,7 +8,7 @@ - PiGallery2 + PiGallery2 - \ No newline at end of file + diff --git a/frontend/app/frame/frame.component.ts b/frontend/app/frame/frame.component.ts index 0c633a4..3ddf015 100644 --- a/frontend/app/frame/frame.component.ts +++ b/frontend/app/frame/frame.component.ts @@ -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(); + } + } diff --git a/frontend/app/gallery/IconPhoto.ts b/frontend/app/gallery/IconPhoto.ts index 798ee01..b9c3b84 100644 --- a/frontend/app/gallery/IconPhoto.ts +++ b/frontend/app/gallery/IconPhoto.ts @@ -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; - } -} \ No newline at end of file + return false; + } +} diff --git a/frontend/app/gallery/Photo.ts b/frontend/app/gallery/Photo.ts index a839b8b..0f2a0f9 100644 --- a/frontend/app/gallery/Photo.ts +++ b/frontend/app/gallery/Photo.ts @@ -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 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 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()); + } -} \ No newline at end of file +} diff --git a/frontend/app/gallery/cache.gallery.service.ts b/frontend/app/gallery/cache.gallery.service.ts index 248508d..d8b9c72 100644 --- a/frontend/app/gallery/cache.gallery.service.ts +++ b/frontend/app/gallery/cache.gallery.service.ts @@ -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; + } + }); + } + + } + } diff --git a/frontend/app/gallery/directory/directory.gallery.component.html b/frontend/app/gallery/directory/directory.gallery.component.html index da1e2c6..1e31d20 100644 --- a/frontend/app/gallery/directory/directory.gallery.component.html +++ b/frontend/app/gallery/directory/directory.gallery.component.html @@ -4,10 +4,10 @@
-
- diff --git a/frontend/app/gallery/directory/directory.gallery.component.ts b/frontend/app/gallery/directory/directory.gallery.component.ts index 5dfda96..b0aa6ef 100644 --- a/frontend/app/gallery/directory/directory.gallery.component.ts +++ b/frontend/app/gallery/directory/directory.gallery.component.ts @@ -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(); } + } } diff --git a/frontend/app/gallery/fullscreen.service.ts b/frontend/app/gallery/fullscreen.service.ts index 9e89b1c..3f351cd 100644 --- a/frontend/app/gallery/fullscreen.service.ts +++ b/frontend/app/gallery/fullscreen.service.ts @@ -5,42 +5,42 @@ import {Event} from "../../../common/event/Event"; export class FullScreenService { - OnFullScreenChange = new Event(); + OnFullScreenChange = new Event(); - 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); + } } diff --git a/frontend/app/gallery/gallery.component.ts b/frontend/app/gallery/gallery.component.ts index 5eee4d8..d0f8c39 100644 --- a/frontend/app/gallery/gallery.component.ts +++ b/frontend/app/gallery/gallery.component.ts @@ -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 = 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 = 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(); + } } diff --git a/frontend/app/gallery/gallery.service.ts b/frontend/app/gallery/gallery.service.ts index 1f32f1a..e378d11 100644 --- a/frontend/app/gallery/gallery.service.ts +++ b/frontend/app/gallery/gallery.service.ts @@ -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> { - this.content = new ContentWrapper(); + public getDirectory(directoryName: string): Promise> { + 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) => { - 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) => { + 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> { - 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) => { - if (!message.error && message.result) { - this.content = message.result; - } - return message; - }); - } - - //TODO: cache (together with normal search) - public instantSearch(text: string): Promise> { - 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) => { - 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> { + 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) => { + if (!message.error && message.result) { + this.content = message.result; + } + return message; + }); + } + + //TODO: cache (together with normal search) + public instantSearch(text: string): Promise> { + 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) => { + if (!message.error && message.result) { + this.content = message.result; + } + return message; + }); + + } + } diff --git a/frontend/app/gallery/grid/GridPhoto.ts b/frontend/app/gallery/grid/GridPhoto.ts index ae44c13..292fda1 100644 --- a/frontend/app/gallery/grid/GridPhoto.ts +++ b/frontend/app/gallery/grid/GridPhoto.ts @@ -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); + } -} \ No newline at end of file +} diff --git a/frontend/app/gallery/grid/GridRowBuilder.ts b/frontend/app/gallery/grid/GridRowBuilder.ts index 0321b49..166f08f 100644 --- a/frontend/app/gallery/grid/GridRowBuilder.ts +++ b/frontend/app/gallery/grid/GridRowBuilder.ts @@ -2,63 +2,63 @@ import {PhotoDTO} from "../../../../common/entities/PhotoDTO"; export class GridRowBuilder { - private photoRow: Array = []; + private photoRow: Array = []; - 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, private startIndex: number, private photoMargin: number, private containerWidth: number) { - this.photoIndex = startIndex; + constructor(private photos: Array, 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 { + 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 { - 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); - }; -} \ No newline at end of file + return height + (this.photoMargin * 2); + }; +} diff --git a/frontend/app/gallery/grid/grid.gallery.component.ts b/frontend/app/gallery/grid/grid.gallery.component.ts index a67b4d4..3c4c1f8 100644 --- a/frontend/app/gallery/grid/grid.gallery.component.ts +++ b/frontend/app/gallery/grid/grid.gallery.component.ts @@ -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; + @ViewChild('gridContainer') gridContainer: ElementRef; + @ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList; - @Input() photos: Array; - @Input() lightbox: GalleryLightboxComponent; + @Input() photos: Array; + @Input() lightbox: GalleryLightboxComponent; - photosToRender: Array = []; - containerWidth: number = 0; + photosToRender: Array = []; + 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; + } } diff --git a/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts b/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts index 01ff058..f1c1691 100644 --- a/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts +++ b/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts @@ -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; } diff --git a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.html b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.html index 9dab905..bec8b9c 100644 --- a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.html +++ b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.html @@ -1,7 +1,7 @@
- + - + @@ -30,4 +30,4 @@
- \ No newline at end of file + diff --git a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts index 849ed59..fcfc678 100644 --- a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts +++ b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts @@ -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 { - 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 { + top: this.imageRef.nativeElement.offsetTop, + left: this.imageRef.nativeElement.offsetLeft, + width: this.imageRef.nativeElement.width, + height: this.imageRef.nativeElement.height + }; + } } diff --git a/frontend/app/gallery/lightbox/lightbox.gallery.component.ts b/frontend/app/gallery/lightbox/lightbox.gallery.component.ts index 742ccf3..c1f5f42 100644 --- a/frontend/app/gallery/lightbox/lightbox.gallery.component.ts +++ b/frontend/app/gallery/lightbox/lightbox.gallery.component.ts @@ -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 = {top: 0, left: 0, width: 0, height: 0}; - public lightboxDimension: 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 = {top: 0, left: 0, width: 0, height: 0}; + public lightboxDimension: Dimension = {top: 0, left: 0, width: 0, height: 0}; + public transition: string = ""; + public blackCanvasOpacity: any = 0; - private activePhoto: GalleryPhotoComponent; - private gridPhotoQL: QueryList; + public activePhoto: GalleryPhotoComponent; + private gridPhotoQL: QueryList; - 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 = { + 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) { + 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 ? 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 = { - 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) { - 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 ? 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 {top: top, left: left, width: width, height: height}; - } + return {top: top, left: left, width: width, height: height}; + } } diff --git a/frontend/app/gallery/lightbox/photo/photo.lightbox.gallery.component.ts b/frontend/app/gallery/lightbox/photo/photo.lightbox.gallery.component.ts index aee4443..9e4185f 100644 --- a/frontend/app/gallery/lightbox/photo/photo.lightbox.gallery.component.ts +++ b/frontend/app/gallery/lightbox/photo/photo.lightbox.gallery.component.ts @@ -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 + } } diff --git a/frontend/app/gallery/map/lightbox/lightbox.map.gallery.component.ts b/frontend/app/gallery/map/lightbox/lightbox.map.gallery.component.ts index 93dc0e3..1f6a735 100644 --- a/frontend/app/gallery/map/lightbox/lightbox.map.gallery.component.ts +++ b/frontend/app/gallery/map/lightbox/lightbox.map.gallery.component.ts @@ -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; - private startPosition = null; - public lightboxDimension: Dimension = {top: 0, left: 0, width: 0, height: 0}; - public mapDimension: 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; + private startPosition = null; + public lightboxDimension: Dimension = {top: 0, left: 0, width: 0, height: 0}; + public mapDimension: 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 = { + 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 = { + 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 = { - 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 = { - 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 ? window.event : e; - switch (event.keyCode) { - case 27: //escape - this.hide(); - break; - } + let event: KeyboardEvent = window.event ? window.event : e; + switch (event.keyCode) { + case 27: //escape + this.hide(); + break; } + } } diff --git a/frontend/app/gallery/map/map.gallery.component.ts b/frontend/app/gallery/map/map.gallery.component.ts index 4dde36f..9ca16f9 100644 --- a/frontend/app/gallery/map/map.gallery.component.ts +++ b/frontend/app/gallery/map/map.gallery.component.ts @@ -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; - @ViewChild(GalleryMapLightboxComponent) mapLightbox: GalleryMapLightboxComponent; + @Input() photos: Array; + @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 { - 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 { + top: this.map.nativeElement.offsetTop, + left: this.map.nativeElement.offsetLeft, + width: this.map.nativeElement.offsetWidth, + height: this.map.nativeElement.offsetHeight + }; + } } diff --git a/frontend/app/gallery/navigator/navigator.gallery.component.ts b/frontend/app/gallery/navigator/navigator.gallery.component.ts index 440e788..14965e0 100644 --- a/frontend/app/gallery/navigator/navigator.gallery.component.ts +++ b/frontend/app/gallery/navigator/navigator.gallery.component.ts @@ -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 = []; + routes: Array = []; - 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; + + + } + } diff --git a/frontend/app/gallery/overlay.service.ts b/frontend/app/gallery/overlay.service.ts index 9b137f8..a3e0382 100644 --- a/frontend/app/gallery/overlay.service.ts +++ b/frontend/app/gallery/overlay.service.ts @@ -4,57 +4,57 @@ import {Event} from "../../../common/event/Event"; @Injectable() export class OverlayService { - OnOverlayChange = new Event(); - private scrollWidth: number = null; + OnOverlayChange = new Event(); + 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; + } } diff --git a/frontend/app/gallery/search/autocomplete.service.ts b/frontend/app/gallery/search/autocomplete.service.ts index 2b363fd..79308d4 100644 --- a/frontend/app/gallery/search/autocomplete.service.ts +++ b/frontend/app/gallery/search/autocomplete.service.ts @@ -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 >> { - return this._networkService.getJson("/autocomplete/" + text); - } + public autoComplete(text: string): Promise>> { + return this._networkService.getJson("/autocomplete/" + text); + } } diff --git a/frontend/app/gallery/search/search.gallery.component.html b/frontend/app/gallery/search/search.gallery.component.html index c304b45..e2e2b37 100644 --- a/frontend/app/gallery/search/search.gallery.component.html +++ b/frontend/app/gallery/search/search.gallery.component.html @@ -2,7 +2,7 @@