diff --git a/common/config/Config.ts b/common/config/Config.ts index ffebbf01..e0acfd1c 100644 --- a/common/config/Config.ts +++ b/common/config/Config.ts @@ -18,6 +18,7 @@ interface SearchConfig { interface ClientConfig { thumbnailSizes:Array; Search:SearchConfig; + concurrentThumbnailGenerations:number; } export class ConfigClass { @@ -29,7 +30,8 @@ export class ConfigClass { searchEnabled: true, instantSearchEnabled: true, autocompleteEnabled: true - } + }, + concurrentThumbnailGenerations: 1 }; public setDatabaseType(type:DatabaseType) { diff --git a/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.css b/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.css new file mode 100644 index 00000000..fa8cc783 --- /dev/null +++ b/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.css @@ -0,0 +1,97 @@ +.static { + width: 100%; + height: 100%; + background-color: #bbbbbb; + color: #7f7f7f; + font-size: 50px; +} + +.static span { + top: calc(50% - 25px); + left: calc(50% - 25px); +} + +.sk-cube-grid { + width: 100%; + height: 100%; +} + +.sk-cube-grid .sk-cube { + width: 33%; + height: 33%; + background-color: #bbbbbb; + float: left; +} + +.sk-cube-grid.animate .sk-cube { + -webkit-animation: sk-cubeGridScaleDelay 4.6s infinite ease-in-out; + animation: sk-cubeGridScaleDelay 4.6s infinite ease-in-out; +} + +.sk-cube-grid.animate .sk-cube1 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} + +.sk-cube-grid.animate .sk-cube2 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} + +.sk-cube-grid.animate .sk-cube3 { + -webkit-animation-delay: 0.8s; + animation-delay: 0.8s; +} + +.sk-cube-grid.animate .sk-cube4 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} + +.sk-cube-grid.animate .sk-cube5 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} + +.sk-cube-grid.animate .sk-cube6 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} + +.sk-cube-grid.animate .sk-cube7 { + -webkit-animation-delay: 0s; + animation-delay: 0s; +} + +.sk-cube-grid.animate .sk-cube8 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} + +.sk-cube-grid.animate .sk-cube9 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.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); + } +} + diff --git a/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.html b/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.html new file mode 100644 index 00000000..ccea2023 --- /dev/null +++ b/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.html @@ -0,0 +1,12 @@ +
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts b/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts new file mode 100644 index 00000000..35c3a7cc --- /dev/null +++ b/frontend/app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts @@ -0,0 +1,19 @@ +/// + +import {Component} from "@angular/core"; + +@Component({ + selector: 'gallery-grid-photo-loading', + templateUrl: 'app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.html', + styleUrls: ['app/gallery/grid/photo/loading/loading.photo.grid.gallery.component.css'], +}) +export class GalleryPhotoLoadingComponent { + + animate = false; + + startAnimation() { + console.log("animate"); + this.animate = true; + } +} + diff --git a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.css b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.css index f3621c9a..81e62fc3 100644 --- a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.css +++ b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.css @@ -1,6 +1,51 @@ img { width: inherit; height: inherit; + + -webkit-animation: fadein 2s; /* Safari, Chrome and Opera > 12.1 */ + -moz-animation: fadein 2s; /* Firefox < 16 */ + -ms-animation: fadein 2s; /* Internet Explorer */ + -o-animation: fadein 2s; /* Opera < 12.1 */ + animation: fadein 2s; +} + +@keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* Firefox < 16 */ +@-moz-keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* Safari, Chrome and Opera > 12.1 */ +@-webkit-keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* Internet Explorer */ +@-ms-keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } } .info { @@ -44,85 +89,3 @@ a { display: inline-block; 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); - } -} - 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 65bb1086..90004a92 100644 --- a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.html +++ b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.html @@ -1,16 +1,8 @@
-
-
-
-
-
-
-
-
-
-
-
+ + +
-import {Component, Input, ElementRef, ViewChild, OnChanges} from "@angular/core"; +import {Component, Input, ElementRef, ViewChild, AfterViewInit} from "@angular/core"; import {IRenderable, Dimension} from "../../../model/IRenderable"; import {GridPhoto} from "../GridPhoto"; import {SearchTypes} from "../../../../../common/entities/AutoCompleteItem"; import {RouterLink} from "@angular/router-deprecated"; import {Config} from "../../../config/Config"; import {ThumbnailLoaderService} from "../thumnailLoader.service"; +import {GalleryPhotoLoadingComponent} from "./loading/loading.photo.grid.gallery.component"; @Component({ selector: 'gallery-grid-photo', templateUrl: 'app/gallery/grid/photo/photo.grid.gallery.component.html', styleUrls: ['app/gallery/grid/photo/photo.grid.gallery.component.css'], - directives: [RouterLink], + directives: [RouterLink, GalleryPhotoLoadingComponent], }) -export class GalleryPhotoComponent implements IRenderable, OnChanges { +export class GalleryPhotoComponent implements IRenderable, AfterViewInit { @Input() gridPhoto:GridPhoto; @ViewChild("image") imageRef:ElementRef; @ViewChild("info") infoDiv:ElementRef; + @ViewChild(GalleryPhotoLoadingComponent) loading:GalleryPhotoLoadingComponent; imageSrc = "#"; showImage = false; @@ -34,21 +36,28 @@ export class GalleryPhotoComponent implements IRenderable, OnChanges { 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(()=> { + ngAfterViewInit() { + //schedule change after Angular checks the model + setImmediate(() => { + if (this.gridPhoto.isThumbnailAvailable()) { this.imageSrc = this.gridPhoto.getThumbnailPath(); - // this.showImage = true; - this.gridPhoto.thumbnailLoaded(); - }).catch((error)=> { - console.error("something bad happened"); - }); - } + this.showImage = true; + } else { + this.thumbnailService.loadImage(this.gridPhoto, + ()=> { //onLoadStarted + this.loading.startAnimation(); + }, + ()=> {//onLoaded + this.imageSrc = this.gridPhoto.getThumbnailPath(); + this.showImage = true; + }, + ()=> {//onError + console.error("something bad happened"); + }); + } + }); } - + getPositionText():string { if (!this.gridPhoto) { diff --git a/frontend/app/gallery/grid/thumnailLoader.service.ts b/frontend/app/gallery/grid/thumnailLoader.service.ts index 915fdcbd..f67fee88 100644 --- a/frontend/app/gallery/grid/thumnailLoader.service.ts +++ b/frontend/app/gallery/grid/thumnailLoader.service.ts @@ -2,64 +2,66 @@ import {Injectable} from "@angular/core"; import {GridPhoto} from "./GridPhoto"; +import {Config} from "../../config/Config"; @Injectable() export class ThumbnailLoaderService { que:Array = []; + runningRequests:number = 0; constructor() { } - loadImage(gridPhoto:GridPhoto):Promise { + loadImage(gridPhoto:GridPhoto, onStartedLoading, onLoad, onError):void { console.log("[LOAD IMG]" + gridPhoto.photo.name); - return new Promise((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; - } + let tmp:ThumbnailTask = null; + for (let i = 0; i < this.que.length; i++) { + if (this.que[i].gridPhoto.getThumbnailPath() == 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(); - }); + } + if (tmp != null) { + tmp.onStartedLoading.push(onStartedLoading); + tmp.onLoad.push(onLoad); + tmp.onError.push(onError); + } else { + this.que.push({ + gridPhoto: gridPhoto, + onStartedLoading: [onStartedLoading], + onLoad: [onLoad], + onError: [onError] + }); + } + this.run(); } - isRunning:boolean = false; run() { - if (this.que.length === 0 || this.isRunning === true) { + if (this.que.length === 0 || this.runningRequests >= Config.Client.concurrentThumbnailGenerations) { return; } - this.isRunning = true; + this.runningRequests++; let task = this.que.shift(); - console.log("loadingstarted: " + task.src); + task.onStartedLoading.forEach(cb=>cb()); + console.log("loadingstarted: " + task.gridPhoto.getThumbnailPath()); let curImg = new Image(); - curImg.src = task.src; + curImg.src = task.gridPhoto.getThumbnailPath(); curImg.onload = () => { - console.log(task.src + "done"); - task.resolve.forEach((resolve:()=>{}) => { - resolve(); - - }); - this.isRunning = false; + console.log(task.gridPhoto.getThumbnailPath() + "done"); + task.gridPhoto.thumbnailLoaded(); + task.onLoad.forEach(cb=>cb()); + this.runningRequests--; this.run(); }; curImg.onerror = (error) => { - console.error(task.src + "error"); - task.reject.forEach((reject:(error)=>{}) => { - reject(error); - }); - this.isRunning = false; + console.error(task.gridPhoto.getThumbnailPath() + "error"); + task.onLoad.forEach(cb=>cb(error)); + this.runningRequests--; this.run(); }; } @@ -67,7 +69,8 @@ export class ThumbnailLoaderService { } interface ThumbnailTask { - src:string; - resolve:Array; - reject:Array; + gridPhoto:GridPhoto; + onStartedLoading:Array; + onLoad:Array; + onError:Array; }