diff --git a/common/entities/PhotoDTO.ts b/common/entities/PhotoDTO.ts index fccf06a3..68d7450d 100644 --- a/common/entities/PhotoDTO.ts +++ b/common/entities/PhotoDTO.ts @@ -39,8 +39,8 @@ export interface PositionMetaData { } export interface GPSMetadata { - latitude?: string; - longitude?: string; + latitude?: number; + longitude?: number; altitude?: string; } \ No newline at end of file diff --git a/frontend/app/app.module.ts b/frontend/app/app.module.ts index c0082091..9ac3112c 100644 --- a/frontend/app/app.module.ts +++ b/frontend/app/app.module.ts @@ -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] diff --git a/frontend/app/gallery/cache.gallery.service.ts b/frontend/app/gallery/cache.gallery.service.ts index bf6f08f9..ea978828 100644 --- a/frontend/app/gallery/cache.gallery.service.ts +++ b/frontend/app/gallery/cache.gallery.service.ts @@ -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; } diff --git a/frontend/app/gallery/directory/directory.gallery.component.html b/frontend/app/gallery/directory/directory.gallery.component.html index 8b6e9149..da1e2c6a 100644 --- a/frontend/app/gallery/directory/directory.gallery.component.html +++ b/frontend/app/gallery/directory/directory.gallery.component.html @@ -1,12 +1,14 @@ -
-
+
-
diff --git a/frontend/app/gallery/directory/directory.gallery.component.ts b/frontend/app/gallery/directory/directory.gallery.component.ts index c3a8008f..21ce2931 100644 --- a/frontend/app/gallery/directory/directory.gallery.component.ts +++ b/frontend/app/gallery/directory/directory.gallery.component.ts @@ -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(); + } + } } diff --git a/frontend/app/gallery/gallery.component.html b/frontend/app/gallery/gallery.component.html index bb78babe..3f924db6 100644 --- a/frontend/app/gallery/gallery.component.html +++ b/frontend/app/gallery/gallery.component.html @@ -10,6 +10,7 @@ + diff --git a/frontend/app/gallery/gallery.service.ts b/frontend/app/gallery/gallery.service.ts index 35808d2c..1f32f1a7 100644 --- a/frontend/app/gallery/gallery.service.ts +++ b/frontend/app/gallery/gallery.service.ts @@ -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> { + + 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) => { + (message: Message) => { 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> { + public search(text: string, type?: SearchTypes): Promise> { 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) => { + (message: Message) => { 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> { + public instantSearch(text: string): Promise> { 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) => { + (message: Message) => { if (!message.error && message.result) { this.content = message.result; } 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 532a8bea..8627aa6a 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 @@
- + - + 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 c1403dc8..489bc018 100644 --- a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts +++ b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts @@ -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 { top: this.imageRef.nativeElement.offsetTop, diff --git a/frontend/app/gallery/grid/thumnailLoader.service.ts b/frontend/app/gallery/grid/thumnailLoader.service.ts index 25ce4d21..9c17ee80 100644 --- a/frontend/app/gallery/grid/thumnailLoader.service.ts +++ b/frontend/app/gallery/grid/thumnailLoader.service.ts @@ -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; diff --git a/frontend/app/gallery/grid/thumnailManager.service.ts b/frontend/app/gallery/grid/thumnailManager.service.ts new file mode 100644 index 00000000..d33a93a3 --- /dev/null +++ b/frontend/app/gallery/grid/thumnailManager.service.ts @@ -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; + } + } +} + 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 26d064e6..07542255 100644 --- a/frontend/app/gallery/map/lightbox/lightbox.map.gallery.component.ts +++ b/frontend/app/gallery/map/lightbox/lightbox.map.gallery.component.ts @@ -18,8 +18,8 @@ export class GalleryMapLightboxComponent implements OnChanges { public mapDimension: 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 = { @@ -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; diff --git a/frontend/app/gallery/map/map.gallery.component.ts b/frontend/app/gallery/map/map.gallery.component.ts index 6c6a8deb..883e0521 100644 --- a/frontend/app/gallery/map/map.gallery.component.ts +++ b/frontend/app/gallery/map/map.gallery.component.ts @@ -13,8 +13,8 @@ export class GalleryMapComponent implements OnChanges, IRenderable { @Input() photos: Array; @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 diff --git a/tsconfig.json b/tsconfig.json index c9afd5c7..9b03be30 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,12 +6,12 @@ "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, + "suppressImplicitAnyIndexErrors": false, "lib": [ "es2015", "dom", "es2015.promise" ], - "suppressImplicitAnyIndexErrors": false, "typeRoots": [ "./node_modules/@types" ]