1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

implementing search

This commit is contained in:
Braun Patrik 2016-05-09 21:43:52 +02:00
parent 225d2b6a8c
commit c93c1aba2d
18 changed files with 218 additions and 40 deletions

View File

@ -7,6 +7,8 @@ import {Config} from "../config/Config";
import {ObjectManagerRepository} from "../model/ObjectManagerRepository";
import {AutoCompleteItem} from "../../common/entities/AutoCompleteItem";
import {ContentWrapper} from "../../common/entities/ConentWrapper";
import {SearchResult} from "../../common/entities/SearchResult";
import {Photo} from "../../common/entities/Photo";
export class GalleryMWs {
@ -27,12 +29,19 @@ export class GalleryMWs {
if (err || !directory) {
return next(new Error(ErrorCodes.GENERAL_ERROR, err));
}
//remove cyclic reference
directory.photos.forEach((photo:Photo) => {
photo.directory = null;
});
req.resultPipe = new ContentWrapper(directory, null);
return next();
});
}
public static loadImage(req:Request, res:Response, next:NextFunction) {
if (!(req.params.imagePath)) {
return next();
@ -49,14 +58,29 @@ export class GalleryMWs {
public static search(req:Request, res:Response, next:NextFunction) {
//TODO: implement
return next(new Error(ErrorCodes.GENERAL_ERROR));
ObjectManagerRepository.getInstance().getSearchManager().search(req.params.text, (err, result:SearchResult) => {
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) {
//TODO: implement
return next(new Error(ErrorCodes.GENERAL_ERROR));
if (!(req.params.text)) {
return next();
}
ObjectManagerRepository.getInstance().getSearchManager().instantSearch(req.params.text, (err, result:SearchResult) => {
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) {

View File

@ -30,7 +30,7 @@ export class DiskManager {
if (DiskManager.isImage(fullFilePath)) {
let dimensions = sizeOf(fullFilePath);
directory.photos.push(new Photo(1, file, dimensions.width, dimensions.height));
directory.photos.push(new Photo(1, file, directory, dimensions.width, dimensions.height));
}
}

View File

@ -1,4 +1,7 @@
import {AutoCompleteItem} from "../../common/entities/AutoCompleteItem";
import {SearchResult} from "../../common/entities/SearchResult";
export interface ISearchManager {
autocomplete(text, cb:(error:any, result:Array<AutoCompleteItem>) => void);
search(text, cb:(error:any, result:SearchResult) => void);
instantSearch(text, cb:(error:any, result:SearchResult) => void);
}

View File

@ -1,5 +1,6 @@
import {AutoCompleteItem} from "../../../common/entities/AutoCompleteItem";
import {ISearchManager} from "../ISearchManager";
import {SearchResult} from "../../../common/entities/SearchResult";
export class SearchManager implements ISearchManager {
@ -8,5 +9,13 @@ export class SearchManager implements ISearchManager {
throw new Error("not implemented");
}
search(text, cb:(error:any, result:SearchResult) => void) {
throw new Error("not implemented");
}
instantSearch(text, cb:(error:any, result:SearchResult) => void) {
throw new Error("not implemented");
}
}

View File

@ -5,6 +5,7 @@ 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 {
@ -22,7 +23,7 @@ export class MongoGalleryManager implements IGalleryManager {
if (err || !res) {
return this.indexDirectory(relativeDirectoryName, cb);
}
return cb(err, res);
return cb(err, this.modelToEntity(res));
});
}
@ -46,12 +47,38 @@ export class MongoGalleryManager implements IGalleryManager {
});
scannedDirectory.photos = arr;
DirectoryModel.create(scannedDirectory, (err)=> {
return cb(err, scannedDirectory);
DirectoryModel.create(scannedDirectory, (err, savedDir)=> {
scannedDirectory.photos.forEach((value:any) => {
value['directory'] = savedDir;
value.save();
});
return cb(err, this.modelToEntity(scannedDirectory));
});
});
}
private modelToEntity(directroy:any):Directory {
console.log("modelToEntity");
// console.log(directroy);
let directoryEntity = new Directory(directroy._id);
Utils.updateKeys(directoryEntity, directroy);
directroy.photos.forEach((photo) => {
let photoEntity = new Photo(null, null, null, null, null);
Utils.updateKeys(photoEntity, photo);
console.log(photoEntity);
directoryEntity.photos.push(photoEntity);
});
directroy.directories.forEach((dir) => {
let dirEntity = new Directory(null, null, null, null, null, null);
Utils.updateKeys(dirEntity, dir);
console.log(dir);
console.log(dirEntity);
directoryEntity.directories.push(dirEntity);
});
return directoryEntity;
}
}

View File

@ -2,6 +2,7 @@ import {AutoCompleteItem, AutoCompeleteTypes} from "../../../common/entities/Aut
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 {
@ -35,6 +36,70 @@ export class MongoSearchManager implements ISearchManager {
});
}
search(text, cb:(error:any, result:SearchResult) => void) {
console.log("instantSearch: " + text);
let result:SearchResult = new SearchResult();
result.searchText = text;
PhotoModel.find({
name: {
$regex: text,
$options: "i"
}
}).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"
}
}).select('name').exec((err, res:Array<any>) => {
if (err || !res) {
return cb(err, null);
}
result.directories = res;
return cb(null, result);
});
});
}
instantSearch(text, cb:(error:any, result:SearchResult) => void) {
console.log("instantSearch: " + text);
let result:SearchResult = new SearchResult();
result.searchText = text;
PhotoModel.find({
name: {
$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:AutoCompeleteTypes) {
let res = [];
values.forEach((value)=> {

View File

@ -1,8 +1,15 @@
import {DatabaseManager} from "../DatabaseManager";
import {Schema} from "mongoose";
export var PhotoModel = DatabaseManager.getInstance().getModel('photo',{
name:String,
width:Number,
height:Number
height: Number,
directory: {
type: Schema.Types.ObjectId,
ref: 'directory'
},
});

View File

@ -20,7 +20,7 @@ export class Utils {
}
public static updateKeys(targetObject, sourceObject) {
Object.keys(sourceObject).forEach((key)=> {
Object.keys(sourceObject).forEach((key)=> {
if (typeof targetObject[key] === "undefined") {
return;
}

View File

@ -1,14 +1,14 @@
import {Utils} from "../Utils";
import {Directory} from "./Directory";
export class Photo {
constructor(public id:number, public name:string, public width:number, public height:number) {
constructor(public id?:number, public name?:string, public directory?:Directory, public width?:number, public height?:number) {
}
public static getThumbnailPath(directory:Directory, photo:Photo) {
return Utils.concatUrls("/api/gallery/content/", directory.path, directory.name, photo.name, "thumbnail");
public static getThumbnailPath(photo:Photo) {
return Utils.concatUrls("/api/gallery/content/", photo.directory.path, photo.directory.name, photo.name, "thumbnail");
}
public static getPhotoPath(directory:Directory, photo:Photo) {
return Utils.concatUrls("/api/gallery/content/", directory.path, directory.name, photo.name);
public static getPhotoPath(photo:Photo) {
return Utils.concatUrls("/api/gallery/content/", photo.directory.path, photo.directory.name, photo.name);
}
}

View File

@ -2,11 +2,8 @@ import {Directory} from "./Directory";
import {Photo} from "./Photo";
export class SearchResult {
public searchText:string;
public directories:Array<Directory>;
public photos:Array<Photo>;
constructor(directories:Array<Directory>, photos:Array<Photo>) {
this.directories = directories;
this.photos = photos;
}
}

View File

@ -7,6 +7,14 @@
<div *ngFor="let directory of _galleryService.content.directory.directories">
<gallery-directory *ngIf="directory" [directory]="directory"></gallery-directory>
</div>
<gallery-grid [directory]="_galleryService.content.directory" [lightbox]="lightbox"></gallery-grid>
<gallery-grid [photos]="_galleryService.content.directory.photos" [lightbox]="lightbox"></gallery-grid>
</div>
<div body class="container" style="width: 100%; padding:0" *ngIf="_galleryService.content.searchResult">
<div> Searching for: {{_galleryService.content.searchResult.searchText}}</div>
<div *ngFor="let directory of _galleryService.content.searchResult.directories">
<gallery-directory *ngIf="directory" [directory]="directory"></gallery-directory>
</div>
<gallery-grid [photos]="_galleryService.content.searchResult.photos" [lightbox]="lightbox"></gallery-grid>
</div>
</app-frame>

View File

@ -4,12 +4,16 @@ import {Injectable} from "@angular/core";
import {NetworkService} from "../model/network/network.service.ts";
import {Message} from "../../../common/entities/Message";
import {ContentWrapper} from "../../../common/entities/ConentWrapper";
import {Photo} from "../../../common/entities/Photo";
import {Directory} from "../../../common/entities/Directory";
@Injectable()
export class GalleryService {
public content:ContentWrapper;
private lastDirectory:Directory;
private searchId:any;
constructor(private _networkService:NetworkService) {
this.content = new ContentWrapper();
}
@ -18,13 +22,21 @@ export class GalleryService {
return this._networkService.getJson("/gallery/content/" + directoryName).then(
(message:Message<ContentWrapper>) => {
if (!message.error && message.result) {
message.result.directory.photos.forEach((photo:Photo) => {
photo.directory = message.result.directory;
});
this.lastDirectory = message.result.directory;
this.content = message.result;
}
return message;
});
}
//TODO: cache
public search(text:string):Promise<Message<ContentWrapper>> {
if (text === null || text === '') {
return Promise.resolve(new Message(null, null));
}
return this._networkService.getJson("/gallery/search/" + text).then(
(message:Message<ContentWrapper>) => {
if (!message.error && message.result) {
@ -34,7 +46,24 @@ export class GalleryService {
});
}
//TODO: cache (together with normal search)
public instantSearch(text:string):Promise<Message<ContentWrapper>> {
if (text === null || text === '') {
this.content.directory = this.lastDirectory;
this.content.searchResult = null;
clearTimeout(this.searchId);
return Promise.resolve(new Message(null, null));
}
if (this.searchId != null) {
clearTimeout(this.searchId);
}
this.searchId = setTimeout(() => {
this.search(text);
this.searchId = null;
}, 3000); //TODO: set timeout to config
return this._networkService.getJson("/gallery/instant-search/" + text).then(
(message:Message<ContentWrapper>) => {
if (!message.error && message.result) {
@ -42,6 +71,7 @@ export class GalleryService {
}
return message;
});
}
}

View File

@ -3,7 +3,6 @@
*ngFor="let gridPhoto of photosToRender"
(click)="lightbox.show(gridPhoto.photo)"
[photo]="gridPhoto.photo"
[directory]="directory"
[style.width.px]="gridPhoto.renderWidth"
[style.height.px]="gridPhoto.renderHeight"
[style.marginLeft.px]="IMAGE_MARGIN"

View File

@ -10,7 +10,6 @@ import {
QueryList,
AfterViewInit
} from "@angular/core";
import {Directory} from "../../../../common/entities/Directory";
import {Photo} from "../../../../common/entities/Photo";
import {GalleryPhotoComponent} from "../photo/photo.gallery.component";
import {GridRowBuilder} from "./GridRowBuilder";
@ -27,7 +26,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
@ViewChild('gridContainer') gridContainer:ElementRef;
@ViewChildren(GalleryPhotoComponent) gridPhotoQL:QueryList<GalleryPhotoComponent>;
@Input() directory:Directory;
@Input() photos:Array<Photo>;
@Input() lightbox:GalleryLightboxComponent;
photosToRender:Array<GridPhoto> = [];
@ -41,7 +40,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
}
ngOnChanges() {
// this.renderPhotos();
this.renderPhotos();
}
onResize() {
@ -92,9 +91,9 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
this.photosToRender = [];
let i = 0;
while (i < this.directory.photos.length) {
while (i < this.photos.length) {
let photoRowBuilder = new GridRowBuilder(this.directory.photos, i, this.IMAGE_MARGIN, containerWidth);
let photoRowBuilder = new GridRowBuilder(this.photos, i, this.IMAGE_MARGIN, containerWidth);
photoRowBuilder.addPhotos(this.TARGET_COL_COUNT);
photoRowBuilder.adjustRowHeightBetween(minRowHeight, maxRowHeight);

View File

@ -150,14 +150,14 @@ export class GalleryLightboxComponent {
if (!this.activePhoto) {
return "";
}
return Photo.getPhotoPath(this.activePhoto.directory, this.activePhoto.photo);
return Photo.getPhotoPath(this.activePhoto.photo);
}
getThumbnailPath() {
if (!this.activePhoto) {
return "";
}
return Photo.getThumbnailPath(this.activePhoto.directory, this.activePhoto.photo);
return Photo.getThumbnailPath(this.activePhoto.photo);
}
private getBodyScrollTop() {

View File

@ -2,7 +2,6 @@
import {Component, Input, ElementRef, ViewChild} from "@angular/core";
import {Photo} from "../../../../common/entities/Photo";
import {Directory} from "../../../../common/entities/Directory";
import {IRenderable, Dimension} from "../../model/IRenderable";
@Component({
@ -11,15 +10,14 @@ import {IRenderable, Dimension} from "../../model/IRenderable";
styleUrls: ['app/gallery/photo/photo.gallery.component.css'],
})
export class GalleryPhotoComponent implements IRenderable {
@Input() photo:Photo;
@Input() directory:Directory;
@Input() photo:Photo;
@ViewChild("image") imageRef:ElementRef;
constructor() {
}
getPhotoPath() {
return Photo.getThumbnailPath(this.directory, this.photo);
return Photo.getThumbnailPath(this.photo);
}

View File

@ -1,8 +1,9 @@
<div class="col-sm-4 col-md-5 pull-right">
<form class="navbar-form" role="search">
<form class="navbar-form" role="search" #SearchForm="ngForm">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search" (keyup)="getSuggestions($event)" (blur)="onFocusLost($event)"
name="srch-term" id="srch-term" autocomplete="off" >
<input type="text" class="form-control" placeholder="Search" (keyup)="onSearchChange($event)"
(blur)="onFocusLost($event)" [(ngModel)]="searchText" #name="ngForm" ngControl="search"
name="srch-term" id="srch-term" autocomplete="off" >
<div class="autocomplete-list" *ngIf="autoCompleteItems.length > 0" >
<div class="autocomplete-item" *ngFor="let item of autoCompleteItems">
@ -11,7 +12,8 @@
</div>
<div class="input-group-btn" style="display: block">
<button class="btn btn-default dropdown-toggle" type="button"><i class="glyphicon glyphicon-search"></i>
<button class="btn btn-default dropdown-toggle" type="button" (click)="onSearch()"><i
class="glyphicon glyphicon-search"></i>
</button>
</div>
</div>

View File

@ -4,21 +4,25 @@ import {Component} from "@angular/core";
import {AutoCompleteService} from "./autocomplete.service";
import {AutoCompleteItem} from "../../../../common/entities/AutoCompleteItem";
import {Message} from "../../../../common/entities/Message";
import {GalleryService} from "../gallery.service";
import {FORM_DIRECTIVES} from "@angular/common";
@Component({
selector: 'gallery-search',
templateUrl: 'app/gallery/search/search.gallery.component.html',
styleUrls: ['app/gallery/search/search.gallery.component.css'],
providers: [AutoCompleteService]
providers: [AutoCompleteService],
directives: [FORM_DIRECTIVES]
})
export class GallerySearchComponent {
autoCompleteItems:Array<AutoCompleteRenderItem> = [];
private searchText:string = "";
constructor(private _autoCompleteService:AutoCompleteService) {
constructor(private _autoCompleteService:AutoCompleteService, private _galleryService:GalleryService) {
}
getSuggestions(event:KeyboardEvent) {
onSearchChange(event:KeyboardEvent) {
let searchText = (<HTMLInputElement>event.target).value;
if (searchText.trim().length > 0) {
this._autoCompleteService.autoComplete(searchText).then((message:Message<Array<AutoCompleteItem>>) => {
@ -32,8 +36,14 @@ export class GallerySearchComponent {
} else {
this.emptyAutoComplete();
}
this._galleryService.instantSearch(searchText);
}
public onSearch() {
this._galleryService.search(this.searchText);
}
private showSuggestions(suggestions:Array<AutoCompleteItem>, searchText:string) {
this.emptyAutoComplete();
suggestions.forEach((item)=> {