1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-11-24 08:42:24 +02:00

updating thumbnail generation

This commit is contained in:
Braun Patrik 2017-03-20 00:01:41 +01:00
parent 0a4716e2f8
commit 879deb63d0
14 changed files with 275 additions and 147 deletions

View File

@ -39,8 +39,8 @@ export interface PositionMetaData {
}
export interface GPSMetadata {
latitude?: string;
longitude?: string;
latitude?: number;
longitude?: number;
altitude?: string;
}

View File

@ -29,6 +29,7 @@ import {StringifyRole} from "./pipes/StringifyRolePipe";
import {Config} from "./config/Config";
import {GalleryMapComponent} from "./gallery/map/map.gallery.component";
import {GalleryMapLightboxComponent} from "./gallery/map/lightbox/lightbox.map.gallery.component";
import {ThumbnailManagerService} from "./gallery/grid/thumnailManager.service";
@NgModule({
imports: [
@ -66,6 +67,7 @@ import {GalleryMapLightboxComponent} from "./gallery/map/lightbox/lightbox.map.g
GalleryService,
AuthenticationService,
ThumbnailLoaderService,
ThumbnailManagerService,
FullScreenService],
bootstrap: [AppComponent]

View File

@ -17,10 +17,21 @@ export class GalleryCacheService {
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);
directory.photos.forEach((photo: PhotoDTO) => {
photo.directory = directory;
});
return directory;
}

View File

@ -1,12 +1,14 @@
<a class="button btn btn-default" [routerLink]="['/gallery', getDirectoryPath()]"
<a #dirContainer class="button btn btn-default" [routerLink]="['/gallery', getDirectoryPath()]"
style="display: inline-block;">
<div class="photo-container">
<div class="photo" *ngIf="photo" [style.background-image]="'url('+photo.getThumbnailPath()+')'"></div>
<div class="photo" *ngIf="thumbnail && thumbnail.available"
[style.background-image]="'url('+thumbnail.src+')'"></div>
<span *ngIf="!photo" class="glyphicon glyphicon-folder-open no-image" aria-hidden="true">
<span *ngIf="!thumbnail || !thumbnail.available" class="glyphicon glyphicon-folder-open no-image"
aria-hidden="true">
</span>
</div>

View File

@ -1,8 +1,9 @@
import {Component, Input, OnChanges} from "@angular/core";
import {Component, Input, OnInit, OnDestroy, ViewChild, ElementRef} from "@angular/core";
import {DirectoryDTO} from "../../../../common/entities/DirectoryDTO";
import {RouterLink} from "@angular/router";
import {Utils} from "../../../../common/Utils";
import {Photo} from "../Photo";
import {Thumbnail, ThumbnailManagerService} from "../grid/thumnailManager.service";
@Component({
selector: 'gallery-directory',
@ -10,26 +11,36 @@ import {Photo} from "../Photo";
styleUrls: ['app/gallery/directory/directory.gallery.component.css'],
providers: [RouterLink],
})
export class GalleryDirectoryComponent implements OnChanges {
export class GalleryDirectoryComponent implements OnInit,OnDestroy {
@Input() directory: DirectoryDTO;
photo: Photo = null;
@ViewChild("dirContainer") container: ElementRef;
thumbnail: Thumbnail = null;
constructor() {
constructor(private thumbnailService: ThumbnailManagerService) {
}
ngOnChanges() {
setImmediate(() => {
if (this.directory.photos.length > 0) {
this.photo = new Photo(this.directory.photos[0], 100, 100);
console.log(this.photo);
}
});
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;
}
getDirectoryPath() {
return Utils.concatUrls(this.directory.path, this.directory.name);
}
ngOnDestroy() {
if (this.thumbnail != null) {
this.thumbnail.destroy();
}
}
}

View File

@ -10,6 +10,7 @@
<gallery-directory *ngFor="let directory of _galleryService.content.directory.directories"
[directory]="directory"></gallery-directory>
<gallery-map [photos]="_galleryService.content.directory.photos"></gallery-map>
<gallery-grid [photos]="_galleryService.content.directory.photos" [lightbox]="lightbox"></gallery-grid>
</div>

View File

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

View File

@ -1,7 +1,7 @@
<div #photoContainer class="photo-container" (mouseover)="hover()" (mouseout)="mouseOut()">
<img #img [src]="image.src" [hidden]="!image.show || loading.show" (load)="onImageLoad()">
<img #img [src]="thumbnail.src" [hidden]="!thumbnail.available">
<gallery-grid-photo-loading [animate]="loading.animate" *ngIf="loading.show">
<gallery-grid-photo-loading [animate]="thumbnail.loading" *ngIf="!thumbnail.available">
</gallery-grid-photo-loading>
<!--Info box -->

View File

@ -1,15 +1,10 @@
import {Component, Input, ElementRef, ViewChild, OnInit, AfterViewInit, OnDestroy} from "@angular/core";
import {Component, Input, ElementRef, ViewChild, OnInit, OnDestroy} from "@angular/core";
import {IRenderable, Dimension} from "../../../model/IRenderable";
import {GridPhoto} from "../GridPhoto";
import {SearchTypes} from "../../../../../common/entities/AutoCompleteItem";
import {RouterLink} from "@angular/router";
import {Config} from "../../../config/Config";
import {
ThumbnailLoaderService,
ThumbnailTaskEntity,
ThumbnailLoadingListener,
ThumbnailLoadingPriority
} from "../thumnailLoader.service";
import {Thumbnail, ThumbnailManagerService} from "../thumnailManager.service";
@Component({
selector: 'gallery-grid-photo',
@ -17,24 +12,24 @@ import {
styleUrls: ['app/gallery/grid/photo/photo.grid.gallery.component.css'],
providers: [RouterLink],
})
export class GalleryPhotoComponent implements IRenderable, OnInit, AfterViewInit, OnDestroy {
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
};
image = {
src: '',
show: false
};
loading = {
animate: false,
show: true
};
thumbnailTask: ThumbnailTaskEntity = null;
loading = {
animate: false,
show: true
};
*/
infoStyle = {
height: 0,
@ -46,65 +41,67 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, AfterViewInit
wasInView: boolean = null;
constructor(private thumbnailService: ThumbnailLoaderService) {
constructor(private thumbnailService: ThumbnailManagerService) {
this.SearchTypes = SearchTypes;
this.searchEnabled = Config.Client.Search.searchEnabled;
}
ngOnInit() {
this.loading.show = true;
//set up befoar 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;
}
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(() => {
/*
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);
}
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);
}
});
}
}
});
}
}*/
ngOnDestroy() {
if (this.thumbnailTask != null) {
this.thumbnailService.removeTask(this.thumbnailTask);
this.thumbnailTask = null;
}
this.thumbnail.destroy();
/*
if (this.thumbnailTask != null) {
this.thumbnailService.removeTask(this.thumbnailTask);
this.thumbnailTask = null;
}*/
}
@ -115,24 +112,10 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, AfterViewInit
onScroll() {
if (this.thumbnailTask != null) {
let isInView = this.isInView();
if (this.wasInView != isInView) {
this.wasInView = isInView;
if (isInView === true) {
if (this.gridPhoto.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.high;
}
} else {
if (this.gridPhoto.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.low;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
}
}
}
let isInView = this.isInView();
if (this.wasInView != isInView) {
this.wasInView = isInView;
this.thumbnail.Visible = isInView;
}
}
@ -157,10 +140,11 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, AfterViewInit
}
onImageLoad() {
this.loading.show = false;
}
/*
onImageLoad() {
this.loading.show = false;
}
*/
public getDimension(): Dimension {
return <Dimension>{
top: this.imageRef.nativeElement.offsetTop,

View File

@ -1,7 +1,7 @@
import {Injectable} from "@angular/core";
import {GridPhoto} from "./GridPhoto";
import {Config} from "../../config/Config";
import {GalleryCacheService} from "../cache.gallery.service";
import {Photo} from "../Photo";
export enum ThumbnailLoadingPriority{
high, medium, low
@ -36,12 +36,12 @@ export class ThumbnailLoaderService {
}
loadImage(gridPhoto: GridPhoto, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
loadImage(photo: Photo, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
let tmp: ThumbnailTask = null;
//is image already qued?
for (let i = 0; i < this.que.length; i++) {
if (this.que[i].gridPhoto.getThumbnailPath() == gridPhoto.getThumbnailPath()) {
if (this.que[i].photo.getThumbnailPath() == photo.getThumbnailPath()) {
tmp = this.que[i];
break;
}
@ -58,7 +58,7 @@ export class ThumbnailLoaderService {
} else {//create new task
this.que.push({
gridPhoto: gridPhoto,
photo: photo,
inProgress: false,
taskEntities: [thumbnailTaskEntity]
});
@ -129,8 +129,8 @@ export class ThumbnailLoaderService {
let curImg = new Image();
curImg.onload = () => {
task.gridPhoto.thumbnailLoaded();
this.galleryChacheService.photoUpdated(task.gridPhoto.photo);
task.photo.thumbnailLoaded();
this.galleryChacheService.photoUpdated(task.photo.photo);
task.taskEntities.forEach((te: ThumbnailTaskEntity) => te.listener.onLoad());
this.taskReady(task);
@ -146,7 +146,7 @@ export class ThumbnailLoaderService {
this.run();
};
curImg.src = task.gridPhoto.getThumbnailPath();
curImg.src = task.photo.getThumbnailPath();
};
}
@ -159,13 +159,12 @@ export interface ThumbnailLoadingListener {
export interface ThumbnailTaskEntity {
priority: ThumbnailLoadingPriority;
listener: ThumbnailLoadingListener;
}
interface ThumbnailTask {
gridPhoto: GridPhoto;
photo: Photo;
inProgress: boolean;
taskEntities: Array<ThumbnailTaskEntity>;

View File

@ -0,0 +1,109 @@
import {Injectable} from "@angular/core";
import {Photo} from "../Photo";
import {ThumbnailLoaderService, ThumbnailLoadingListener, ThumbnailTaskEntity} from "./thumnailLoader.service";
export enum ThumbnailLoadingPriority{
high, medium, low
}
@Injectable()
export class ThumbnailManagerService {
constructor(private thumbnailLoader: ThumbnailLoaderService) {
}
public getThumbnail(photo: Photo) {
return new Thumbnail(photo, this.thumbnailLoader);
}
}
export class Thumbnail {
private available: boolean = false;
private src: string = null;
private loading: boolean = false;
private thumbnailTask: ThumbnailTaskEntity;
constructor(private photo: Photo, private thumbnailService: ThumbnailLoaderService) {
if (this.photo.isThumbnailAvailable()) {
this.src = this.photo.getThumbnailPath();
this.available = true;
} else if (this.photo.isReplacementThumbnailAvailable()) {
this.src = this.photo.getReplacementThumbnailPath();
this.available = true;
}
if (!this.photo.isThumbnailAvailable()) {
setImmediate(() => {
let listener: ThumbnailLoadingListener = {
onStartedLoading: () => { //onLoadStarted
this.loading = true;
},
onLoad: () => {//onLoaded
this.src = this.photo.getThumbnailPath();
this.available = true;
this.loading = false;
this.thumbnailTask = null;
},
onError: (error) => {//onError
this.thumbnailTask = null;
//TODO: handle error
//TODO: not an error if its from cache
console.error("something bad happened");
console.error(error);
}
};
if (this.photo.isReplacementThumbnailAvailable()) {
this.thumbnailTask = this.thumbnailService.loadImage(this.photo, ThumbnailLoadingPriority.medium, listener);
} else {
this.thumbnailTask = this.thumbnailService.loadImage(this.photo, ThumbnailLoadingPriority.high, listener);
}
});
}
}
set Visible(visible: boolean) {
if (visible === true) {
if (this.photo.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.high;
}
} else {
if (this.photo.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.low;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
}
}
}
get Available() {
return this.available;
}
get Src() {
return this.src;
}
get Loading() {
return this.loading;
}
destroy() {
if (this.thumbnailTask != null) {
this.thumbnailService.removeTask(this.thumbnailTask);
this.thumbnailTask = null;
}
}
}

View File

@ -18,8 +18,8 @@ export class GalleryMapLightboxComponent implements OnChanges {
public mapDimension: Dimension = <Dimension>{top: 0, left: 0, width: 0, height: 0};
private visible = false;
private opacity = 1.0;
mapPhotos: Array<{latitude: string, longitude: string, iconUrl}> = [];
mapCenter = {latitude: "0", longitude: "0"};
mapPhotos: Array<{latitude: number, longitude: number, iconUrl}> = [];
mapCenter = {latitude: 0, longitude: 0};
@ViewChild("root") elementRef: ElementRef;
@ -33,21 +33,10 @@ export class GalleryMapLightboxComponent implements OnChanges {
//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,
iconUrl: Utils.concatUrls("/api/gallery/content/", p.directory.path, p.directory.name, p.name, "icon")
};
});
if (this.mapPhotos.length > 0) {
this.mapCenter = this.mapPhotos[0];
if (this.visible == false) {
return;
}
this.showImages();
}
public show(position: Dimension) {
@ -65,6 +54,7 @@ export class GalleryMapLightboxComponent implements OnChanges {
this.map.triggerResize();
document.getElementsByTagName('body')[0].style.overflow = 'hidden';
this.showImages();
setImmediate(() => {
this.lightboxDimension = <Dimension>{
@ -91,11 +81,28 @@ export class GalleryMapLightboxComponent implements OnChanges {
this.opacity = 0.0;
setTimeout(() => {
this.visible = false;
this.mapPhotos = [];
}, 500);
}
showImages() {
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,
iconUrl: Utils.concatUrls("/api/gallery/content/", p.directory.path, p.directory.name, p.name, "icon")
};
});
if (this.mapPhotos.length > 0) {
this.mapCenter = this.mapPhotos[0];
}
}
private getBodyScrollTop(): number {
return window.scrollY;

View File

@ -13,8 +13,8 @@ export class GalleryMapComponent implements OnChanges, IRenderable {
@Input() photos: Array<PhotoDTO>;
@ViewChild(GalleryMapLightboxComponent) mapLightbox: GalleryMapLightboxComponent;
mapPhotos: Array<{latitude: string, longitude: string, iconUrl}> = [];
mapCenter = {latitude: "0", longitude: "0"};
mapPhotos: Array<{latitude: number, longitude: number, iconUrl}> = [];
mapCenter = {latitude: 0, longitude: 0};
@ViewChild("map") map: ElementRef;
//TODO: fix zooming

View File

@ -6,12 +6,12 @@
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"suppressImplicitAnyIndexErrors": false,
"lib": [
"es2015",
"dom",
"es2015.promise"
],
"suppressImplicitAnyIndexErrors": false,
"typeRoots": [
"./node_modules/@types"
]