mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-01-18 04:58:59 +02:00
implementing basic thumbnail loading
This commit is contained in:
parent
c5e3d09302
commit
08d6dc994e
@ -10,7 +10,7 @@ Config.Server = {
|
|||||||
port: 80,
|
port: 80,
|
||||||
imagesFolder: "/demo/images",
|
imagesFolder: "/demo/images",
|
||||||
thumbnailFolder: "/demo/TEMP",
|
thumbnailFolder: "/demo/TEMP",
|
||||||
databaseType: DatabaseType.mongoDB
|
databaseType: DatabaseType.memory
|
||||||
};
|
};
|
||||||
|
|
||||||
ConfigLoader.init(Config, path.join(__dirname, './../../config.json'), [["PORT", "Server-port"]]);
|
ConfigLoader.init(Config, path.join(__dirname, './../../config.json'), [["PORT", "Server-port"]]);
|
||||||
|
@ -11,19 +11,11 @@ export class ObjectManagerRepository {
|
|||||||
private _searchManager:ISearchManager;
|
private _searchManager:ISearchManager;
|
||||||
private static _instance:ObjectManagerRepository = null;
|
private static _instance:ObjectManagerRepository = null;
|
||||||
|
|
||||||
public static InitMongoManagers() {
|
|
||||||
let MongoGalleryManager = require("./mongoose/MongoGalleryManager");
|
|
||||||
let MongoUserManager = require("./mongoose/MongoUserManager");
|
|
||||||
let MongoSearchManager = require("./mongoose/MongoSearchManager");
|
|
||||||
ObjectManagerRepository.getInstance().setGalleryManager(new MongoGalleryManager());
|
|
||||||
ObjectManagerRepository.getInstance().setUserManager(new MongoUserManager());
|
|
||||||
ObjectManagerRepository.getInstance().setSearchManager(new MongoSearchManager());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MemoryMongoManagers() {
|
public static MemoryMongoManagers() {
|
||||||
let GalleryManager = require("./memory/GalleryManager");
|
let GalleryManager = require("./memory/GalleryManager").GalleryManager;
|
||||||
let UserManager = require("./memory/UserManager");
|
let UserManager = require("./memory/UserManager").UserManager;
|
||||||
let SearchManager = require("./memory/SearchManager");
|
let SearchManager = require("./memory/SearchManager").SearchManager;
|
||||||
ObjectManagerRepository.getInstance().setGalleryManager(new GalleryManager());
|
ObjectManagerRepository.getInstance().setGalleryManager(new GalleryManager());
|
||||||
ObjectManagerRepository.getInstance().setUserManager(new UserManager());
|
ObjectManagerRepository.getInstance().setUserManager(new UserManager());
|
||||||
ObjectManagerRepository.getInstance().setSearchManager(new SearchManager());
|
ObjectManagerRepository.getInstance().setSearchManager(new SearchManager());
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
///<reference path="../../../typings/index.d.ts"/>
|
|
||||||
|
|
||||||
import * as mongoose from "mongoose";
|
|
||||||
import {Schema} from "mongoose";
|
|
||||||
|
|
||||||
export class DatabaseManager {
|
|
||||||
private static _instance:DatabaseManager = null;
|
|
||||||
private connectionError = false;
|
|
||||||
private errorObject = null;
|
|
||||||
private connectionOpen = false;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
mongoose.connect('mongodb://localhost/EQZT6L');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getInstance(onError?:(err)=>void, onConnected?:() =>void) {
|
|
||||||
if (DatabaseManager._instance === null) {
|
|
||||||
DatabaseManager._instance = new DatabaseManager();
|
|
||||||
}
|
|
||||||
return DatabaseManager._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public onConnectionError(onError:(err) => void){
|
|
||||||
if (this.connectionError === true) {
|
|
||||||
return onError(DatabaseManager._instance.errorObject);
|
|
||||||
}
|
|
||||||
mongoose.connection.once('error', (err) => {
|
|
||||||
this.connectionError = true;
|
|
||||||
this.errorObject = err;
|
|
||||||
onError(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public onConnected(onConnected:() => void){
|
|
||||||
if (this.connectionOpen === true) {
|
|
||||||
return onConnected();
|
|
||||||
}
|
|
||||||
mongoose.connection.once('open', (err) => {
|
|
||||||
this.connectionOpen = true;
|
|
||||||
onConnected();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getModel(name:string, schema:any) {
|
|
||||||
return mongoose.model(name, new Schema(schema));
|
|
||||||
}
|
|
||||||
|
|
||||||
public disconnect() {
|
|
||||||
mongoose.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
public isConnectionError() {
|
|
||||||
return this.connectionError;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
import * as path from "path";
|
|
||||||
import {Directory} from "../../../common/entities/Directory";
|
|
||||||
import {IGalleryManager} from "../IGalleryManager";
|
|
||||||
import {DiskManager} from "../DiskManger";
|
|
||||||
import {Utils} from "../../../common/Utils";
|
|
||||||
import {DirectoryModel} from "./entities/DirectoryModel";
|
|
||||||
import {PhotoModel} from "./entities/PhotoModel";
|
|
||||||
import {Photo} from "../../../common/entities/Photo";
|
|
||||||
|
|
||||||
export class MongoGalleryManager implements IGalleryManager {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public listDirectory(relativeDirectoryName, cb:(error:any, result:Directory) => void) {
|
|
||||||
let directoryName = path.basename(relativeDirectoryName);
|
|
||||||
let directoryParent = path.join(path.dirname(relativeDirectoryName), "/");
|
|
||||||
|
|
||||||
|
|
||||||
DirectoryModel.findOne({
|
|
||||||
name: directoryName,
|
|
||||||
path: directoryParent
|
|
||||||
}).populate('photos').populate('directories').exec((err, res:any) => {
|
|
||||||
if (err || !res) {
|
|
||||||
return this.indexDirectory(relativeDirectoryName, cb);
|
|
||||||
}
|
|
||||||
return cb(err, this.modelToEntity(res));
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public indexDirectory(relativeDirectoryName, cb:(error:any, result:Directory) => void) {
|
|
||||||
DiskManager.scanDirectory(relativeDirectoryName, (err, scannedDirectory)=> {
|
|
||||||
let arr = [];
|
|
||||||
scannedDirectory.directories.forEach((value) => {
|
|
||||||
let dir = new DirectoryModel(value);
|
|
||||||
Utils.setKeys(dir, value);
|
|
||||||
dir.save();
|
|
||||||
arr.push(dir);
|
|
||||||
});
|
|
||||||
scannedDirectory.directories = arr;
|
|
||||||
arr = [];
|
|
||||||
scannedDirectory.photos.forEach((value) => {
|
|
||||||
let p = new PhotoModel(value);
|
|
||||||
Utils.setKeys(p, value);
|
|
||||||
p.save();
|
|
||||||
arr.push(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
scannedDirectory.photos = arr;
|
|
||||||
DirectoryModel.create(scannedDirectory, (err, savedDir)=> {
|
|
||||||
scannedDirectory.photos.forEach((value:any) => {
|
|
||||||
value['directory'] = savedDir;
|
|
||||||
value.save();
|
|
||||||
});
|
|
||||||
return cb(err, this.modelToEntity(savedDir));
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private modelToEntity(directoryModel:any):Directory {
|
|
||||||
|
|
||||||
let directoryEntity = new Directory(directoryModel._id, directoryModel.name, directoryModel.path, directoryModel.lastupdate, [], []);
|
|
||||||
|
|
||||||
directoryModel.photos.forEach((photo) => {
|
|
||||||
let photoEntity = new Photo(photo._id, photo.name, directoryEntity, photo.metadata);
|
|
||||||
directoryEntity.photos.push(photoEntity);
|
|
||||||
});
|
|
||||||
|
|
||||||
directoryModel.directories.forEach((dir) => {
|
|
||||||
let dirEntity = new Directory(dir._id, dir.name, dir.path, dir.lastupdate, [], []);
|
|
||||||
directoryEntity.directories.push(dirEntity);
|
|
||||||
});
|
|
||||||
|
|
||||||
return directoryEntity;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,182 +0,0 @@
|
|||||||
import {AutoCompleteItem, SearchTypes} from "../../../common/entities/AutoCompleteItem";
|
|
||||||
import {ISearchManager} from "../ISearchManager";
|
|
||||||
import {DirectoryModel} from "./entities/DirectoryModel";
|
|
||||||
import {PhotoModel} from "./entities/PhotoModel";
|
|
||||||
import {SearchResult} from "../../../common/entities/SearchResult";
|
|
||||||
|
|
||||||
export class MongoSearchManager implements ISearchManager {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
autocomplete(text, cb:(error:any, result:Array<AutoCompleteItem>) => void) {
|
|
||||||
|
|
||||||
console.log("autocomplete: " + text);
|
|
||||||
let items:Array<AutoCompleteItem> = [];
|
|
||||||
let promises = [];
|
|
||||||
|
|
||||||
promises.push(
|
|
||||||
PhotoModel.find({name: {$regex: text, $options: "i"}})
|
|
||||||
.limit(10).select('name').exec().then((res:Array<any>)=> {
|
|
||||||
items = items.concat(this.encapsulateAutoComplete(res.map(r => r.name), SearchTypes.image));
|
|
||||||
}));
|
|
||||||
|
|
||||||
promises.push(
|
|
||||||
PhotoModel.find({"metadata.positionData.city": {$regex: text, $options: "i"}})
|
|
||||||
.limit(10).select('metadata.positionData.city').exec().then((res:Array<any>)=> {
|
|
||||||
items = items.concat(this.encapsulateAutoComplete(res.map(r => r.metadata.positionData.city), SearchTypes.position));
|
|
||||||
}));
|
|
||||||
|
|
||||||
promises.push(
|
|
||||||
PhotoModel.find({"metadata.positionData.state": {$regex: text, $options: "i"}})
|
|
||||||
.limit(10).select('metadata.positionData.state').exec().then((res:Array<any>)=> {
|
|
||||||
items = items.concat(this.encapsulateAutoComplete(res.map(r => r.metadata.positionData.state), SearchTypes.position));
|
|
||||||
}));
|
|
||||||
|
|
||||||
promises.push(
|
|
||||||
PhotoModel.find({"metadata.positionData.country": {$regex: text, $options: "i"}})
|
|
||||||
.limit(10).select('metadata.positionData.country').exec().then((res:Array<any>)=> {
|
|
||||||
items = items.concat(this.encapsulateAutoComplete(res.map(r => r.metadata.positionData.country), SearchTypes.position));
|
|
||||||
}));
|
|
||||||
|
|
||||||
//TODO: fix caseinsensitivity
|
|
||||||
promises.push(
|
|
||||||
PhotoModel.find({"metadata.keywords": {$regex: text, $options: "i"}})
|
|
||||||
.limit(10).select('metadata.keywords').exec().then((res:Array<any>)=> {
|
|
||||||
res.forEach((photo)=> {
|
|
||||||
items = items.concat(this.encapsulateAutoComplete(photo.metadata.keywords.filter(k => k.indexOf(text) != -1), SearchTypes.keyword));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
promises.push(
|
|
||||||
DirectoryModel.find({
|
|
||||||
name: {$regex: text, $options: "i"}
|
|
||||||
}).limit(10).select('name').exec().then((res:Array<any>)=> {
|
|
||||||
items = items.concat(this.encapsulateAutoComplete(res.map(r => r.name), SearchTypes.directory));
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
Promise.all(promises).then(()=> {
|
|
||||||
return cb(null, this.autoCompleteItemsUnique(items));
|
|
||||||
}).catch((err)=> {
|
|
||||||
console.error(err);
|
|
||||||
return cb(err, null);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
search(text:string, searchType:SearchTypes, cb:(error:any, result:SearchResult) => void) {
|
|
||||||
console.log("search: " + text + ", type:" + searchType);
|
|
||||||
let result:SearchResult = new SearchResult();
|
|
||||||
|
|
||||||
let promises = [];
|
|
||||||
let photoFilterOR = [];
|
|
||||||
|
|
||||||
result.searchText = text;
|
|
||||||
result.searchType = searchType;
|
|
||||||
|
|
||||||
|
|
||||||
if (!searchType || searchType === SearchTypes.image) {
|
|
||||||
photoFilterOR.push({name: {$regex: text, $options: "i"}});
|
|
||||||
}
|
|
||||||
if (!searchType || searchType === SearchTypes.position) {
|
|
||||||
photoFilterOR.push({"metadata.positionData.city": {$regex: text, $options: "i"}});
|
|
||||||
photoFilterOR.push({"metadata.positionData.state": {$regex: text, $options: "i"}});
|
|
||||||
photoFilterOR.push({"metadata.positionData.country": {$regex: text, $options: "i"}});
|
|
||||||
}
|
|
||||||
if (!searchType || searchType === SearchTypes.keyword) {
|
|
||||||
photoFilterOR.push({"metadata.keywords": {$regex: text, $options: "i"}});
|
|
||||||
}
|
|
||||||
|
|
||||||
let photoFilter = {};
|
|
||||||
if (photoFilterOR.length == 1) {
|
|
||||||
photoFilter = photoFilterOR[0];
|
|
||||||
} else {
|
|
||||||
photoFilter = {$or: photoFilterOR};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!searchType || photoFilterOR.length > 0) {
|
|
||||||
promises.push(PhotoModel.find(photoFilter).populate('directory', 'name path').exec().then((res:Array<any>) => {
|
|
||||||
result.photos = res;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!searchType || searchType === SearchTypes.directory) {
|
|
||||||
promises.push(DirectoryModel.find({
|
|
||||||
name: {
|
|
||||||
$regex: text,
|
|
||||||
$options: "i"
|
|
||||||
}
|
|
||||||
}).exec().then((res:Array<any>) => {
|
|
||||||
result.directories = res;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all(promises).then(()=> {
|
|
||||||
return cb(null, result);
|
|
||||||
}).catch((err)=> {
|
|
||||||
console.error(err);
|
|
||||||
return cb(err, null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
instantSearch(text, cb:(error:any, result:SearchResult) => void) {
|
|
||||||
console.log("instantSearch: " + text);
|
|
||||||
let result:SearchResult = new SearchResult();
|
|
||||||
result.searchText = text;
|
|
||||||
PhotoModel.find({
|
|
||||||
$or: [
|
|
||||||
{name: {$regex: text, $options: "i"}},
|
|
||||||
{"metadata.positionData.city": {$regex: text, $options: "i"}},
|
|
||||||
{"metadata.positionData.state": {$regex: text, $options: "i"}},
|
|
||||||
{"metadata.positionData.country": {$regex: text, $options: "i"}},
|
|
||||||
{"metadata.keywords": {$regex: text, $options: "i"}}
|
|
||||||
]
|
|
||||||
|
|
||||||
}).limit(10).populate('directory', 'name path').exec((err, res:Array<any>) => {
|
|
||||||
if (err || !res) {
|
|
||||||
return cb(err, null);
|
|
||||||
}
|
|
||||||
result.photos = res;
|
|
||||||
|
|
||||||
DirectoryModel.find({
|
|
||||||
name: {
|
|
||||||
$regex: text,
|
|
||||||
$options: "i"
|
|
||||||
}
|
|
||||||
}).limit(10).exec((err, res:Array<any>) => {
|
|
||||||
if (err || !res) {
|
|
||||||
return cb(err, null);
|
|
||||||
}
|
|
||||||
result.directories = res;
|
|
||||||
return cb(null, result);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private encapsulateAutoComplete(values:Array<string>, type:SearchTypes) {
|
|
||||||
let res = [];
|
|
||||||
values.forEach((value)=> {
|
|
||||||
res.push(new AutoCompleteItem(value, type));
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private autoCompleteItemsUnique(array:Array<AutoCompleteItem>) {
|
|
||||||
var a = array.concat();
|
|
||||||
for (var i = 0; i < a.length; ++i) {
|
|
||||||
for (var j = i + 1; j < a.length; ++j) {
|
|
||||||
if (a[i].equals(a[j]))
|
|
||||||
a.splice(j--, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
import {User, UserRoles} from "../../../common/entities/User";
|
|
||||||
import {IUserManager} from "../IUserManager";
|
|
||||||
import {UserModel} from "./entities/UserModel";
|
|
||||||
|
|
||||||
export class MongoUserManager implements IUserManager {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public findOne(filter, cb:(error:any, result:User) => void) {
|
|
||||||
return UserModel.findOne(filter, function (err, result:any) {
|
|
||||||
return cb(err, result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public find(filter, cb:(error:any, result:Array<User>) => void) {
|
|
||||||
UserModel.find(filter, function (err, result:Array<any>) {
|
|
||||||
return cb(err, result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public createUser(user, cb:(error:any, result:User) => void) {
|
|
||||||
UserModel.create(user, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
public deleteUser(id:number, cb:(error:any) => void) {
|
|
||||||
UserModel.remove({id: id}, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public changeRole(id:number, newRole:UserRoles, cb:(error:any, result:string) => void) {
|
|
||||||
return UserModel.update({id: id}, {role: newRole}, function (err) {
|
|
||||||
if (!err) {
|
|
||||||
return cb(err, "ok")
|
|
||||||
}
|
|
||||||
return cb(err, null);
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public changePassword(request:any, cb:(error:any, result:string) => void) {
|
|
||||||
throw new Error("not implemented"); //TODO: implement
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import {DatabaseManager} from "../DatabaseManager";
|
|
||||||
import {Schema} from "mongoose";
|
|
||||||
|
|
||||||
export var DirectoryModel = DatabaseManager.getInstance().getModel('directory', {
|
|
||||||
name: String,
|
|
||||||
path: String,
|
|
||||||
lastUpdate: Date,
|
|
||||||
directories: [{
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'directory'
|
|
||||||
}],
|
|
||||||
photos: [{
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'photo'
|
|
||||||
}]
|
|
||||||
});
|
|
@ -1,39 +0,0 @@
|
|||||||
import {DatabaseManager} from "../DatabaseManager";
|
|
||||||
import {Schema} from "mongoose";
|
|
||||||
|
|
||||||
|
|
||||||
export var PhotoModel = DatabaseManager.getInstance().getModel('photo', {
|
|
||||||
name: String,
|
|
||||||
directory: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'directory'
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
keywords: [String],
|
|
||||||
cameraData: {
|
|
||||||
ISO: Number,
|
|
||||||
maker: String,
|
|
||||||
fStop: Number,
|
|
||||||
exposure: Number,
|
|
||||||
focalLength: Number,
|
|
||||||
lens: String
|
|
||||||
},
|
|
||||||
positionData: {
|
|
||||||
GPSData: {
|
|
||||||
latitude: Number,
|
|
||||||
longitude: Number,
|
|
||||||
altitude: Number
|
|
||||||
},
|
|
||||||
country: String,
|
|
||||||
state: String,
|
|
||||||
city: String
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
width: Number,
|
|
||||||
height: Number
|
|
||||||
},
|
|
||||||
creationDate: Date
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
|||||||
import {DatabaseManager} from "../DatabaseManager";
|
|
||||||
export var UserModel = DatabaseManager.getInstance().getModel('user', {
|
|
||||||
name: {type: String, index: {unique: true}},
|
|
||||||
password: String,
|
|
||||||
role: Number
|
|
||||||
});
|
|
@ -13,7 +13,6 @@ import {ErrorRouter} from "./routes/ErrorRouter";
|
|||||||
import {SharingRouter} from "./routes/SharingRouter";
|
import {SharingRouter} from "./routes/SharingRouter";
|
||||||
import {DatabaseType} from "./../common/config/Config";
|
import {DatabaseType} from "./../common/config/Config";
|
||||||
import {ObjectManagerRepository} from "./model/ObjectManagerRepository";
|
import {ObjectManagerRepository} from "./model/ObjectManagerRepository";
|
||||||
import {DatabaseManager} from "./model/mongoose/DatabaseManager";
|
|
||||||
import {Config} from "./config/Config";
|
import {Config} from "./config/Config";
|
||||||
|
|
||||||
|
|
||||||
@ -61,13 +60,7 @@ export class Server {
|
|||||||
if (Config.Server.databaseType === DatabaseType.memory) {
|
if (Config.Server.databaseType === DatabaseType.memory) {
|
||||||
ObjectManagerRepository.MemoryMongoManagers();
|
ObjectManagerRepository.MemoryMongoManagers();
|
||||||
} else {
|
} else {
|
||||||
ObjectManagerRepository.InitMongoManagers();
|
throw new Error("not implemented alternative mangers");
|
||||||
DatabaseManager.getInstance().onConnectionError(
|
|
||||||
()=> {
|
|
||||||
console.error("MongoDB connection error. Falling back to memory Object Managers");
|
|
||||||
ObjectManagerRepository.MemoryMongoManagers();
|
|
||||||
Config.setDatabaseType(DatabaseType.memory);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new PublicRouter(this.app);
|
new PublicRouter(this.app);
|
||||||
|
@ -15,6 +15,7 @@ export class Utils {
|
|||||||
|
|
||||||
url += part + "/";
|
url += part + "/";
|
||||||
}
|
}
|
||||||
|
url = url.replace("//", "/");
|
||||||
|
|
||||||
return url.substring(0, url.length - 1);
|
return url.substring(0, url.length - 1);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export enum DatabaseType{
|
export enum DatabaseType{
|
||||||
memory, mongoDB
|
memory
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServerConfig {
|
interface ServerConfig {
|
||||||
|
@ -4,7 +4,8 @@ export class Photo {
|
|||||||
constructor(public id?:number,
|
constructor(public id?:number,
|
||||||
public name?:string,
|
public name?:string,
|
||||||
public directory?:Directory,
|
public directory?:Directory,
|
||||||
public metadata?:PhotoMetadata) {
|
public metadata?:PhotoMetadata,
|
||||||
|
public readyThumbnails:Array<number> = []) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import {UserService} from "./model/network/user.service.ts";
|
|||||||
import {GalleryService} from "./gallery/gallery.service";
|
import {GalleryService} from "./gallery/gallery.service";
|
||||||
import {AdminComponent} from "./admin/admin.component";
|
import {AdminComponent} from "./admin/admin.component";
|
||||||
import {NetworkService} from "./model/network/network.service";
|
import {NetworkService} from "./model/network/network.service";
|
||||||
|
import {ThumbnailLoaderService} from "./gallery/grid/thumnailLoader.service";
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -23,7 +24,8 @@ import {NetworkService} from "./model/network/network.service";
|
|||||||
NetworkService,
|
NetworkService,
|
||||||
UserService,
|
UserService,
|
||||||
GalleryService,
|
GalleryService,
|
||||||
AuthenticationService]
|
AuthenticationService,
|
||||||
|
ThumbnailLoaderService]
|
||||||
})
|
})
|
||||||
@RouteConfig([
|
@RouteConfig([
|
||||||
{
|
{
|
||||||
|
@ -6,9 +6,24 @@ export class GridPhoto {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumbnailPath() {
|
|
||||||
|
thumbnailLoaded() {
|
||||||
|
if (!this.isThumbnailAvailable()) {
|
||||||
|
this.photo.readyThumbnails.push(this.getThumbnailSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getThumbnailSize() {
|
||||||
let renderSize = Math.sqrt(this.renderWidth * this.renderHeight);
|
let renderSize = Math.sqrt(this.renderWidth * this.renderHeight);
|
||||||
let size = Utils.findClosest(renderSize, Config.Client.thumbnailSizes);
|
return Utils.findClosest(renderSize, Config.Client.thumbnailSizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
isThumbnailAvailable() {
|
||||||
|
return this.photo.readyThumbnails.indexOf(this.getThumbnailSize()) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
return Utils.concatUrls("/api/gallery/content/", this.photo.directory.path, this.photo.directory.name, this.photo.name, "thumbnail", size.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,3 +45,84 @@ a {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid .sk-cube {
|
||||||
|
width: 33%;
|
||||||
|
height: 33%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
float: left;
|
||||||
|
-webkit-animation: sk-cubeGridScaleDelay 4.6s infinite ease-in-out;
|
||||||
|
animation: sk-cubeGridScaleDelay 4.6s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid .sk-cube1 {
|
||||||
|
-webkit-animation-delay: 2.4s;
|
||||||
|
animation-delay: 2.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid .sk-cube2 {
|
||||||
|
-webkit-animation-delay: 2.6s;
|
||||||
|
animation-delay: 2.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid .sk-cube3 {
|
||||||
|
-webkit-animation-delay: 2.8s;
|
||||||
|
animation-delay: 2.8s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid .sk-cube4 {
|
||||||
|
-webkit-animation-delay: 2.2s;
|
||||||
|
animation-delay: 2.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid .sk-cube5 {
|
||||||
|
-webkit-animation-delay: 2.4s;
|
||||||
|
animation-delay: 2.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid .sk-cube6 {
|
||||||
|
-webkit-animation-delay: 2.6s;
|
||||||
|
animation-delay: 2.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid .sk-cube7 {
|
||||||
|
-webkit-animation-delay: 2s;
|
||||||
|
animation-delay: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid .sk-cube8 {
|
||||||
|
-webkit-animation-delay: 2.2s;
|
||||||
|
animation-delay: 2.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-cube-grid .sk-cube9 {
|
||||||
|
-webkit-animation-delay: 2.4s;
|
||||||
|
animation-delay: 2.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes sk-cubeGridScaleDelay {
|
||||||
|
0%, 70%, 100% {
|
||||||
|
-webkit-transform: scale3D(1, 1, 1);
|
||||||
|
transform: scale3D(1, 1, 1);
|
||||||
|
}
|
||||||
|
35% {
|
||||||
|
-webkit-transform: scale3D(0, 0, 1);
|
||||||
|
transform: scale3D(0, 0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sk-cubeGridScaleDelay {
|
||||||
|
0%, 70%, 100% {
|
||||||
|
-webkit-transform: scale3D(1, 1, 1);
|
||||||
|
transform: scale3D(1, 1, 1);
|
||||||
|
}
|
||||||
|
35% {
|
||||||
|
-webkit-transform: scale3D(0, 0, 1);
|
||||||
|
transform: scale3D(0, 0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,20 @@
|
|||||||
<div class="photo-container" (mouseover)="hover()" (mouseout)="mouseOut()">
|
<div class="photo-container" (mouseover)="hover()" (mouseout)="mouseOut()">
|
||||||
<img #image [src]="gridPhoto.getThumbnailPath()">
|
<img #image [src]="imageSrc" [hidden]="!showImage">
|
||||||
<div class="info" #info [style.margin-top.px]="-infoStyle.height" [style.background]="infoStyle.background">
|
<div class="sk-cube-grid" *ngIf="!showImage">
|
||||||
|
<div class="sk-cube sk-cube1"></div>
|
||||||
|
<div class="sk-cube sk-cube2"></div>
|
||||||
|
<div class="sk-cube sk-cube3"></div>
|
||||||
|
<div class="sk-cube sk-cube4"></div>
|
||||||
|
<div class="sk-cube sk-cube5"></div>
|
||||||
|
<div class="sk-cube sk-cube6"></div>
|
||||||
|
<div class="sk-cube sk-cube7"></div>
|
||||||
|
<div class="sk-cube sk-cube8"></div>
|
||||||
|
<div class="sk-cube sk-cube9"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--Info box -->
|
||||||
|
<div #info [hidden]="!showImage" class="info" [style.margin-top.px]="-infoStyle.height"
|
||||||
|
[style.background]="infoStyle.background">
|
||||||
<div class="photo-name">{{gridPhoto.photo.name}}</div>
|
<div class="photo-name">{{gridPhoto.photo.name}}</div>
|
||||||
|
|
||||||
<div class="photo-position" *ngIf="gridPhoto.photo.metadata.positionData">
|
<div class="photo-position" *ngIf="gridPhoto.photo.metadata.positionData">
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
///<reference path="../../../../browser.d.ts"/>
|
///<reference path="../../../../browser.d.ts"/>
|
||||||
|
|
||||||
import {Component, Input, ElementRef, ViewChild} from "@angular/core";
|
import {Component, Input, ElementRef, ViewChild, OnChanges} from "@angular/core";
|
||||||
import {IRenderable, Dimension} from "../../../model/IRenderable";
|
import {IRenderable, Dimension} from "../../../model/IRenderable";
|
||||||
import {GridPhoto} from "../GridPhoto";
|
import {GridPhoto} from "../GridPhoto";
|
||||||
import {SearchTypes} from "../../../../../common/entities/AutoCompleteItem";
|
import {SearchTypes} from "../../../../../common/entities/AutoCompleteItem";
|
||||||
import {RouterLink} from "@angular/router-deprecated";
|
import {RouterLink} from "@angular/router-deprecated";
|
||||||
import {Config} from "../../../config/Config";
|
import {Config} from "../../../config/Config";
|
||||||
|
import {ThumbnailLoaderService} from "../thumnailLoader.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gallery-grid-photo',
|
selector: 'gallery-grid-photo',
|
||||||
@ -13,23 +14,42 @@ import {Config} from "../../../config/Config";
|
|||||||
styleUrls: ['app/gallery/grid/photo/photo.grid.gallery.component.css'],
|
styleUrls: ['app/gallery/grid/photo/photo.grid.gallery.component.css'],
|
||||||
directives: [RouterLink],
|
directives: [RouterLink],
|
||||||
})
|
})
|
||||||
export class GalleryPhotoComponent implements IRenderable {
|
export class GalleryPhotoComponent implements IRenderable, OnChanges {
|
||||||
@Input() gridPhoto:GridPhoto;
|
@Input() gridPhoto:GridPhoto;
|
||||||
@ViewChild("image") imageRef:ElementRef;
|
@ViewChild("image") imageRef:ElementRef;
|
||||||
@ViewChild("info") infoDiv:ElementRef;
|
@ViewChild("info") infoDiv:ElementRef;
|
||||||
|
|
||||||
|
imageSrc = "#";
|
||||||
|
showImage = false;
|
||||||
infoStyle = {
|
infoStyle = {
|
||||||
height: 0,
|
height: 0,
|
||||||
background: ""
|
background: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
SearchTypes:any = [];
|
SearchTypes:any = [];
|
||||||
searchEnabled:boolean = true;
|
searchEnabled:boolean = true;
|
||||||
|
|
||||||
constructor() {
|
constructor(private thumbnailService:ThumbnailLoaderService) {
|
||||||
this.SearchTypes = SearchTypes;
|
this.SearchTypes = SearchTypes;
|
||||||
this.searchEnabled = Config.Client.Search.searchEnabled;
|
this.searchEnabled = Config.Client.Search.searchEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
if (this.gridPhoto.isThumbnailAvailable()) {
|
||||||
|
this.imageSrc = this.gridPhoto.getThumbnailPath();
|
||||||
|
// this.showImage = true;
|
||||||
|
} else {
|
||||||
|
this.thumbnailService.loadImage(this.gridPhoto).then(()=> {
|
||||||
|
this.imageSrc = this.gridPhoto.getThumbnailPath();
|
||||||
|
// this.showImage = true;
|
||||||
|
this.gridPhoto.thumbnailLoaded();
|
||||||
|
}).catch((error)=> {
|
||||||
|
console.error("something bad happened");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
getPositionText():string {
|
getPositionText():string {
|
||||||
if (!this.gridPhoto) {
|
if (!this.gridPhoto) {
|
||||||
return ""
|
return ""
|
||||||
|
73
frontend/app/gallery/grid/thumnailLoader.service.ts
Normal file
73
frontend/app/gallery/grid/thumnailLoader.service.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
///<reference path="../../../browser.d.ts"/>
|
||||||
|
|
||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {GridPhoto} from "./GridPhoto";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ThumbnailLoaderService {
|
||||||
|
|
||||||
|
que:Array<ThumbnailTask> = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage(gridPhoto:GridPhoto):Promise<void> {
|
||||||
|
console.log("[LOAD IMG]" + gridPhoto.photo.name);
|
||||||
|
return new Promise<void>((resolve:Function, reject:Function)=> {
|
||||||
|
let tmp:ThumbnailTask = null;
|
||||||
|
for (let i = 0; i < this.que.length; i++) {
|
||||||
|
if (this.que[i].src == gridPhoto.getThumbnailPath()) {
|
||||||
|
tmp = this.que[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tmp != null) {
|
||||||
|
tmp.resolve.push(resolve);
|
||||||
|
tmp.reject.push(reject);
|
||||||
|
} else {
|
||||||
|
this.que.push({src: gridPhoto.getThumbnailPath(), resolve: [resolve], reject: [reject]});
|
||||||
|
}
|
||||||
|
this.run();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning:boolean = false;
|
||||||
|
|
||||||
|
run() {
|
||||||
|
if (this.que.length === 0 || this.isRunning === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isRunning = true;
|
||||||
|
let task = this.que.shift();
|
||||||
|
console.log("loadingstarted: " + task.src);
|
||||||
|
|
||||||
|
let curImg = new Image();
|
||||||
|
curImg.src = task.src;
|
||||||
|
curImg.onload = () => {
|
||||||
|
console.log(task.src + "done");
|
||||||
|
task.resolve.forEach((resolve:()=>{}) => {
|
||||||
|
resolve();
|
||||||
|
|
||||||
|
});
|
||||||
|
this.isRunning = false;
|
||||||
|
this.run();
|
||||||
|
};
|
||||||
|
|
||||||
|
curImg.onerror = (error) => {
|
||||||
|
console.error(task.src + "error");
|
||||||
|
task.reject.forEach((reject:(error)=>{}) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
this.isRunning = false;
|
||||||
|
this.run();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThumbnailTask {
|
||||||
|
src:string;
|
||||||
|
resolve:Array<Function>;
|
||||||
|
reject:Array<Function>;
|
||||||
|
}
|
@ -11,7 +11,7 @@ module.exports = {
|
|||||||
library: ['peer']
|
library: ['peer']
|
||||||
},
|
},
|
||||||
// Turn on sourcemaps
|
// Turn on sourcemaps
|
||||||
//devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['', '.webpack.js', '.web.js', '.ts', '.js'],
|
extensions: ['', '.webpack.js', '.web.js', '.ts', '.js'],
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user