From 49bea44b05f318dcf7ce2477c70e25f73de5df5a Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 13 Jan 2019 20:18:18 +0100 Subject: [PATCH] adding persons support for frontend --- backend/model/ObjectManagerRepository.ts | 2 +- backend/model/sql/SearchManager.ts | 60 +++++++++++++++--- backend/model/threading/MetadataLoader.ts | 12 +++- common/entities/AutoCompleteItem.ts | 9 +-- demo/images/IMG_5910.jpg | Bin 711659 -> 712419 bytes demo/images/IMG_6253.jpg | Bin 904058 -> 905152 bytes demo/images/IMG_6297.jpg | Bin 462831 -> 463924 bytes demo/images/IMG_9398-2.jpg | Bin 452490 -> 455762 bytes demo/images/IMG_9516.jpg | Bin 540292 -> 542910 bytes .../photo/photo.grid.gallery.component.css | 14 ++-- .../photo/photo.grid.gallery.component.html | 16 +++-- .../photo/photo.grid.gallery.component.ts | 55 ++++++++-------- .../navigator.gallery.component.html | 1 + .../search/search.gallery.component.html | 1 + 14 files changed, 117 insertions(+), 53 deletions(-) diff --git a/backend/model/ObjectManagerRepository.ts b/backend/model/ObjectManagerRepository.ts index 01a8cfda..c66b12de 100644 --- a/backend/model/ObjectManagerRepository.ts +++ b/backend/model/ObjectManagerRepository.ts @@ -113,7 +113,7 @@ export class ObjectManagerRepository { const UserManager = require('./sql/UserManager').UserManager; const SearchManager = require('./sql/SearchManager').SearchManager; const SharingManager = require('./sql/SharingManager').SharingManager; - const IndexingTaskManager = require('./sql/IndexingManager').IndexingTaskManager; + const IndexingTaskManager = require('./sql/IndexingTaskManager').IndexingTaskManager; const IndexingManager = require('./sql/IndexingManager').IndexingManager; const PersonManager = require('./sql/PersonManager').PersonManager; ObjectManagerRepository.getInstance().GalleryManager = new GalleryManager(); diff --git a/backend/model/sql/SearchManager.ts b/backend/model/sql/SearchManager.ts index b2c44de1..982c0725 100644 --- a/backend/model/sql/SearchManager.ts +++ b/backend/model/sql/SearchManager.ts @@ -6,6 +6,8 @@ import {PhotoEntity} from './enitites/PhotoEntity'; import {DirectoryEntity} from './enitites/DirectoryEntity'; import {MediaEntity} from './enitites/MediaEntity'; import {VideoEntity} from './enitites/VideoEntity'; +import {PersonEntry} from './enitites/PersonEntry'; +import {FaceRegionEntry} from './enitites/FaceRegionEntry'; export class SearchManager implements ISearchManager { @@ -29,7 +31,7 @@ export class SearchManager implements ISearchManager { let result: AutoCompleteItem[] = []; const photoRepository = connection.getRepository(PhotoEntity); const videoRepository = connection.getRepository(VideoEntity); - const mediaRepository = connection.getRepository(MediaEntity); + const personRepository = connection.getRepository(PersonEntry); const directoryRepository = connection.getRepository(DirectoryEntity); @@ -45,6 +47,14 @@ export class SearchManager implements ISearchManager { .filter(k => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchTypes.keyword)); }); + result = result.concat(this.encapsulateAutoComplete((await personRepository + .createQueryBuilder('person') + .select('DISTINCT(person.name)') + .where('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) + .limit(5) + .getRawMany()) + .map(r => r.name), SearchTypes.person)); + (await photoRepository .createQueryBuilder('photo') .select('photo.metadata.positionData.country as country, ' + @@ -112,16 +122,19 @@ export class SearchManager implements ISearchManager { resultOverflow: false }; - let repostiroy = connection.getRepository(MediaEntity); + let repository = connection.getRepository(MediaEntity); + const faceRepository = connection.getRepository(FaceRegionEntry); if (searchType === SearchTypes.photo) { - repostiroy = connection.getRepository(PhotoEntity); + repository = connection.getRepository(PhotoEntity); } else if (searchType === SearchTypes.video) { - repostiroy = connection.getRepository(VideoEntity); + repository = connection.getRepository(VideoEntity); } - const query = repostiroy.createQueryBuilder('media') - .innerJoinAndSelect('media.directory', 'directory') + const query = repository.createQueryBuilder('media') + .leftJoinAndSelect('media.directory', 'directory') + .leftJoin('media.metadata.faces', 'faces') + .leftJoin('faces.person', 'person') .orderBy('media.metadata.creationDate', 'ASC'); @@ -136,6 +149,9 @@ export class SearchManager implements ISearchManager { if (!searchType || searchType === SearchTypes.photo) { query.orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}); } + if (!searchType || searchType === SearchTypes.person) { + query.orWhere('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}); + } if (!searchType || searchType === SearchTypes.position) { query.orWhere('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) @@ -147,9 +163,20 @@ export class SearchManager implements ISearchManager { query.orWhere('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}); } - result.media = await query - .limit(2001) - .getMany(); + + result.media = (await query + .limit(5000).getMany()).slice(0, 2001); + + for (let i = 0; i < result.media.length; i++) { + const faces = (await faceRepository + .createQueryBuilder('faces') + .leftJoinAndSelect('faces.person', 'person') + .where('faces.media = :media', {media: result.media[i].id}) + .getMany()).map(fE => ({name: fE.person.name, box: fE.box})); + if (faces.length > 0) { + result.media[i].metadata.faces = faces; + } + } if (result.media.length > 2000) { result.resultOverflow = true; @@ -181,6 +208,8 @@ export class SearchManager implements ISearchManager { resultOverflow: false }; + const faceRepository = connection.getRepository(FaceRegionEntry); + result.media = await connection .getRepository(MediaEntity) .createQueryBuilder('media') @@ -191,10 +220,23 @@ export class SearchManager implements ISearchManager { .orWhere('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .orWhere('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) + .orWhere('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .innerJoinAndSelect('media.directory', 'directory') + .leftJoin('media.metadata.faces', 'faces') + .leftJoin('faces.person', 'person') .limit(10) .getMany(); + for (let i = 0; i < result.media.length; i++) { + const faces = (await faceRepository + .createQueryBuilder('faces') + .leftJoinAndSelect('faces.person', 'person') + .where('faces.media = :media', {media: result.media[i].id}) + .getMany()).map(fE => ({name: fE.person.name, box: fE.box})); + if (faces.length > 0) { + result.media[i].metadata.faces = faces; + } + } result.directories = await connection .getRepository(DirectoryEntity) diff --git a/backend/model/threading/MetadataLoader.ts b/backend/model/threading/MetadataLoader.ts index 27123fc8..34af8896 100644 --- a/backend/model/threading/MetadataLoader.ts +++ b/backend/model/threading/MetadataLoader.ts @@ -4,7 +4,7 @@ import {Config} from '../../../common/config/private/Config'; import {Logger} from '../../Logger'; import * as fs from 'fs'; import * as sizeOf from 'image-size'; -import {OrientationTypes, ExifParserFactory} from 'ts-exif-parser'; +import {ExifParserFactory, OrientationTypes} from 'ts-exif-parser'; import {IptcParser} from 'ts-node-iptc'; import {FFmpegFactory} from '../FFmpegFactory'; import {FfprobeData} from 'fluent-ffmpeg'; @@ -147,10 +147,16 @@ export class MetadataLoader { try { const iptcData = IptcParser.parse(data); - if (iptcData.country_or_primary_location_name || iptcData.province_or_state || iptcData.city) { + if (iptcData.country_or_primary_location_name) { metadata.positionData = metadata.positionData || {}; metadata.positionData.country = iptcData.country_or_primary_location_name.replace(/\0/g, '').trim(); + } + if (iptcData.province_or_state) { + metadata.positionData = metadata.positionData || {}; metadata.positionData.state = iptcData.province_or_state.replace(/\0/g, '').trim(); + } + if (iptcData.city) { + metadata.positionData = metadata.positionData || {}; metadata.positionData.city = iptcData.city.replace(/\0/g, '').trim(); } if (iptcData.caption) { @@ -160,7 +166,7 @@ export class MetadataLoader { metadata.creationDate = (iptcData.date_time ? iptcData.date_time.getTime() : metadata.creationDate); } catch (err) { - // Logger.debug(LOG_TAG, 'Error parsing iptc data', fullPath, err); + Logger.debug(LOG_TAG, 'Error parsing iptc data', fullPath, err); } metadata.creationDate = metadata.creationDate || 0; diff --git a/common/entities/AutoCompleteItem.ts b/common/entities/AutoCompleteItem.ts index 9fe6e5aa..f61323df 100644 --- a/common/entities/AutoCompleteItem.ts +++ b/common/entities/AutoCompleteItem.ts @@ -1,9 +1,10 @@ export enum SearchTypes { directory = 1, - keyword = 2, - position = 3, - photo = 4, - video = 5 + person = 2, + keyword = 3, + position = 5, + photo = 6, + video = 7 } export class AutoCompleteItem { diff --git a/demo/images/IMG_5910.jpg b/demo/images/IMG_5910.jpg index 852d0a6899d951e6ea093412f0cf3cd469636998..2ca467ad26e2fbc9d8bb0dd560df7b484b9c6b0a 100644 GIT binary patch delta 2169 zcmcIlPiP}m7;n<8Zmp}k+PW>N>~yp$bd#A$CY|Kjq^5sEZFWnOrHi-e%u6yl$;`et zY1&f|1ur7#I|&~2A__eSJ?udQz39;u6w#C5MUWLdE(rR)nMvBTwiLm@m%Q(L-=E+2 z=Y9G8?#!=W%zU;uI(X^x4-ZyGzrPlJ?s4|#>?o7fd_-WbT8JyEENIz;GApZT<+_lR z6-|=Uk~|xi6(na+QW3(Bm>1YDY9S&VOsD^fF!QFiA?!BnCPj^QEly}otb3k|B&pqQ zCmPt(P2JPm4tdRL)@sCQxe3E*NYtp~hE64dYnIcbk{J4+-jdZ|AEloHY!|Q5%ASiQ zkl-feYR??16$sY3-o$Rhl>$93>S@@7-wxYWt?m)WX|Qqa)9EZ^6IW6dC5uv7Bx^G~ zIhi|k(WdSJ!km~o_)(jWKC5J84R0sYabs4=#!XXI2Qq7qLG60w75MYQ|MP+;kD)`RwRY{nWdV)ErPZ1c;@@yR1 z6l@P{@;9>O(8d?|HeEHapsnp!u>qQXoCUoWo@^^$uy?Gc;MK8EzSFV@!FCWEt_1eS z^7vX{1MIDG;qWRr1)ey-!wA5bm>R@#J!s5vn3)qW`<0>Ir4lMy6w0y}8V7Z0tn9%= z-e38_|JL3sejFT%=jn$iP;Yt1L+v_l=0up`f9W@~SJjb4Tc^}AgiJzao#OLP_jlqL zI(D!4W=xnf2t^gAi5FXBr`wkR^Sf(f0CH+=0`>bjZqdhYVzV#to|E}AoCHRFepU&_ z-TEJc^U@ifx@LX87VOq3nN6MHDzIX&zdSZ_bi#WHJ$lrZO_j!?FmqSd$Iq!f)gPkPsFs@E0=36KyZer@Rh=KX54mAs| zYg^c?IEO0=Mi3a(&WTDYtMZBDY&|DtQ?O_PuGO@>oakD+*cPR(w+;;hufwwlN2Biz z2gO}Fe04$>uj9e)a$w};gv>u;_vVJ)0N^Jde@`LEY(d_OW+YiXugPVApg`s;hyPc<#!=0 zhSVpdU}6(gw=mI(QMU};F8{aP=Wj}Yuz(4BuzU%hV-21f__Pc+msWU+y9o9Q|3A#L zsj|uB@~!`c+>M)Tee1Ddu`SN$|Gr=OjTu|%Cx4!dyfQEtn_iua4h%#GE=)#;m~?yW z>ST0c;E9v-!9Lmg;9&Xo*X7IB3-5pP;+MjM?^eGO-oE@3eIvI2R%|~46a^Xtx&Sl; T^bpX)Ko^0A@5c5YxhMSt^`XEX delta 1925 zcmaF7SNrvL?G4T%%=`;KZ*~*8BG07kI{Bfw=;T;a_Q@6|W=y}oQ*!ZW{_#crAb+-$tB39IVGmUO`G5@!45HvbMnSO5g%le;p)I9 zqZnjUkz3&FYvqwy40LHFvf;>1o#-VtxzZyUZoxz^^T{Hf^HBJ&JeO%9TME~2qmRXo z$&lVPqTGP6 z3e^b#VZeN6L{Ugg-tEIrlpB!k!sZHiTw!EYn2)igKCs;6Z9XC-xB+exHfJD37fBA8 zywOL}l$bOL2?r!=ChPf28bNiU6hy#c48+Hi@5FIfI9cCc!VoM6DqyUF5=%1k((R1F zMFETlD%46cOL9`}z_vK%luUkVVmX<`-O>n>#epJ_lmRZbph|(7Qc{bPi!uvJGV}8= zw7Ox_ilPBr*18}W0dgkT1xSLECCshj>}-59lZ*0;^V3QKGV)9EVGcJ17D3o$AeJMU zV+Rola&ZIt1n7G!U})r~mLvjslNEzf80kk^P8N&e<5VwKx3~NMHe}ai-6&ImH8PA* z49wE1?lMda3``6xlNUzKVPx2B8+|NT;LNw3^&8C(B}=JtW+%zANHq(uZx>$A2*gZ4 b%nZaVK+FonY(UHo#2i4(xm|cY*9Cn5a0mg5 diff --git a/demo/images/IMG_6253.jpg b/demo/images/IMG_6253.jpg index 507ac5a641bcf52d9c5623a9fd43e9107c3d320f..9cc007cf98b6af9f845a08f009ffd2c54b18e8f4 100644 GIT binary patch delta 2117 zcmcIlO>7%g5cW?(n;)uDsKJUwUbYPdioHKxd+o(`;x_#eB48ycQsA^+zt~H6z1z3z z*ikAu5=cm#5KrP5jtGeZk`KKhPF#ATf-8a>mm+aRNHFVN|Ez1G^uS8v-I+J@y_tFQ zKL7QF^!=C8w?9UsS1%m(ev0OnBA0%l>nK5AU(SXRtuB|7yvQLbpW?HenBm_*0>?`% zmtnbVlH+AACks6CAEt!pqvdiK^pNBG%e0qn^%jvu<2v<42_XBe! za#<9`YE=|!NlD4^Noh~aCY7qFB-7apm*ym1$ffz-bE}p4E-KViS%sY(lax?_)b`|U ze9!;VwNgcKh<3NYx(lEp@5^&x1EHYUt?GwZb^PdSN_~V=*7vn0avB(I-)m`vV7-sj z^+0{}JKN>4oqXf2X=6ik@YqT}8xP*iVYDqYZ*x<=t=Uk`fxr29ImmWy>^rieI?(ov4C76h2-;1(n$Hj@iW5TVx-P*Y>%15z_r58<&HpGZ_%jw{UGL#@Y!O zUQ7Vy7QwhV6=?kgpg~js>YCn~URL=8BtIwsRCx~$L71A<;y4ujGZTZxEoNU@7#sXy zHz$2BQk0#EpaOkaik~jvj9(`$oHl}1q*=I<9`nds9+@EbB3KHsu}dFCW((V9i>MeV zYv5V7EM3F39dpDjFqNRl?IOcx^O8HHuG#>VPYVOJ)zq9KV`&|%_ovX^xC?XU?ff>} zBfpJ-yX^R-BeWE9j^ZjNa)Yo&i47cBZ+DRbpG5q>`uU0S`Bh=f9s&`cr{4_$b2 zSZH*u)4RX+>6_=jdHBKZTk#6IzdHNc`4>Jr{wQ|%N$fBT5&?;V%z(^-JOgqD Q$UMjb$l`<8;j^{MzuLm7Y5)KL delta 1437 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 h+WH_By+3 zFqa~=X*u-LbPnw)Y9y2^s(gslNNKOVCApL{7Rk9X$1 zneV;%-n{4W!+*hxM}uEn3Vipd_cJzl_1kZr_OnbU=??TRZXt$c80I{~1Xs2qOegJk z(0+AGbmaZC|235{C=!LSD2zlBiG)37-lHu*KO^wO*42lvO0TGDe0+a+8;~|n^LXIHd zK3ocrUxO0?PLSRhT6O0MIiF9CAmR7K8xvM6XWx6WN_xIFM z3R-F*Gf@YieLzke%ylyzVRH+ zwg$Rz3}_w1hkL`oxYQc*;xWh#7$5dzq0%A~?;iv0qWF+!7il0Pt+{>9i%kZl*WRJ) zjzDXf;X{C(&Qfwg>>u=`So63a0+=|GzWOFxC-ER`Kh2VNrMKv}E)C71NIYui!jx8& z^GPltjy6V>l4d2jvR1_f=d2gkmU()Z=kT(omu}Leba}34u3a&lgx{5-S8gdqoa8*& z&H;Dz{Cb&ct}80G3+r_3?uHwNDQv2SR<>}kfvt7S#Kk1c>p*(A5dji+{Z z(jSvrTIzlt3fyH*1&3~g=%06%=^+24QL>LY>$y#Z*f?{V-rFS@htUu_#&n^Gh@vpW zro8fTS5EFtUFpp->_R5QV&?n;y`|zT$XJSnO{VtM)JK!uUk3kvy7SZR)gS)a)_?qY l_IYg~{=vCVhBr5dHv_b>w4I`@gSJlEx}FVhzVhL-{{Y%yrrH1i delta 1886 zcmdltQL*K(!US?<857$seT6n0UD+tIOyLNB9OPIOi9o zDwygS>KQ57@p5rao+{(W0;D-NUzb_WH2IGFg2@LABsNb`5M-L%ZJ@UKw9->94Uj@k zPMefutK_28#FG3XhybTeQA(OsaB3lx%Q;zAPuvr%meWQbMb1VaT{FnE;?kt7)Z`Lm z)0`61;imChNU=jqhhriE-FT(6Bj7V{^Y7`3>8mD?m26EM}wH7nGVprj_-?SP3^C)mHbS)YLpO?1cq`k*TT9 zKqPQG9&!3vg}>}jUS22nM+*i2R5#K^=*NujvJB{SD5!&b@2!pH)~EzQd; zu~jO_tVqpK(ubyr$p_7ZH6hv|j)9kBpu!gFI;cD+=j0b=#$q9r1*x`5Zi&gM;KCcI z4g@AUnMXmR2*`t!Aug%K$wiq3C7JoK%m|Y~lr9jJ2wQFRbIa3pfkjqOYC2G#53taL zI2BEz7%Ylvj|tQwh-i?D8&D0fbhHANkGZKOi6EYRML}Y6c4~=2YF>)1dbzs2-DcL- zXddRb&Lxvu9Zdz+$uKf7Ffp*mFh(&jORKt1KILd=ZqC2~%todL<_5+xOg0SMhDIia z#s=C3Kp?|phb(E2EIHZ2NnIe7fj1Nw?x`sX!6k_$sl}Vio$8zfW`5Y;BH?4u_ck)I jPQ7=zS#$iv_V|g6K+FWh%s|Wn#H>Kfwmp6#d;E3)VIJGu 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 62ed8659..ea791be0 100644 --- a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.css +++ b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.css @@ -92,7 +92,7 @@ a { width: 100%; } -.video-indicator{ +.video-indicator { font-size: large; padding: 5px; position: absolute; @@ -102,12 +102,16 @@ a { background-color: rgba(0, 0, 0, 0.5); color: white; transition: background-color .3s ease-out; - -moz-transition: background-color .3s ease-out; + -moz-transition: background-color .3s ease-out; -webkit-transition: background-color .3s ease-out; - -o-transition: background-color .3s ease-out; - -ms-transition: background-color .3s ease-out; + -o-transition: background-color .3s ease-out; + -ms-transition: background-color .3s ease-out; } -.photo-container:hover .video-indicator{ +.photo-container:hover .video-indicator { background-color: rgba(0, 0, 0, 0.8); } + +.photo-keywords .oi-person{ + margin-right: 2px; +} 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 f7862539..d8421177 100644 --- a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.html +++ b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.html @@ -36,12 +36,18 @@ -
- +
+ #{{keyword}} - #{{keyword}} - , + [routerLink]="['/search', keyword.value, {type: SearchTypes[keyword.type]}]" [ngSwitch]="keyword.type"> + #{{keyword.value}} + + #{{keyword.value}} + ,
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 8af6b460..43e56ed2 100644 --- a/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts +++ b/frontend/app/gallery/grid/photo/photo.grid.gallery.component.ts @@ -5,9 +5,8 @@ import {SearchTypes} from '../../../../../common/entities/AutoCompleteItem'; import {RouterLink} from '@angular/router'; import {Thumbnail, ThumbnailManagerService} from '../../thumbnailManager.service'; import {Config} from '../../../../../common/config/public/Config'; -import {AnimationBuilder} from '@angular/animations'; import {PageHelper} from '../../../model/page.helper'; -import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; +import {PhotoDTO, PhotoMetadata} from '../../../../../common/entities/PhotoDTO'; @Component({ selector: 'app-gallery-grid-photo', @@ -22,6 +21,7 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy { @ViewChild('photoContainer') container: ElementRef; thumbnail: Thumbnail; + keywords: { value: string, type: SearchTypes }[] = null; infoBar = { marginTop: 0, visible: false, @@ -34,17 +34,40 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy { wasInView: boolean = null; - constructor(private thumbnailService: ThumbnailManagerService, - private _animationBuilder: AnimationBuilder) { + constructor(private thumbnailService: ThumbnailManagerService) { this.SearchTypes = SearchTypes; this.searchEnabled = Config.Client.Search.enabled; } - ngOnInit() { - this.thumbnail = this.thumbnailService.getThumbnail(this.gridPhoto); + get ScrollListener(): boolean { + return !this.thumbnail.Available && !this.thumbnail.Error; } + get Title(): string { + if (Config.Client.Other.captionFirstNaming === false) { + return this.gridPhoto.media.name; + } + if ((this.gridPhoto.media).metadata.caption) { + if ((this.gridPhoto.media).metadata.caption.length > 20) { + return (this.gridPhoto.media).metadata.caption.substring(0, 17) + '...'; + } + return (this.gridPhoto.media).metadata.caption; + } + return this.gridPhoto.media.name; + } + + ngOnInit() { + this.thumbnail = this.thumbnailService.getThumbnail(this.gridPhoto); + const metadata = this.gridPhoto.media.metadata as PhotoMetadata; + if ((metadata.keywords && metadata.keywords.length > 0) || + (metadata.faces && metadata.faces.length > 0)) { + this.keywords = (metadata.faces || []).map(f => ({value: f.name, type: SearchTypes.person})) + .concat((metadata.keywords || []).map(k => ({value: k, type: SearchTypes.keyword}))); + } + + } + ngOnDestroy() { this.thumbnail.destroy(); @@ -53,16 +76,11 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy { } } - isInView(): boolean { return PageHelper.ScrollY < this.container.nativeElement.offsetTop + this.container.nativeElement.clientHeight && PageHelper.ScrollY + window.innerHeight > this.container.nativeElement.offsetTop; } - get ScrollListener(): boolean { - return !this.thumbnail.Available && !this.thumbnail.Error; - } - onScroll() { if (this.thumbnail.Available === true || this.thumbnail.Error === true) { return; @@ -74,7 +92,6 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy { } } - getPositionText(): string { if (!this.gridPhoto || !this.gridPhoto.isPhoto()) { return ''; @@ -84,7 +101,6 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy { (this.gridPhoto.media).metadata.positionData.country; } - mouseOver() { this.infoBar.visible = true; if (this.animationTimer != null) { @@ -124,19 +140,6 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy { } - get Title(): string { - if (Config.Client.Other.captionFirstNaming === false) { - return this.gridPhoto.media.name; - } - if ((this.gridPhoto.media).metadata.caption) { - if ((this.gridPhoto.media).metadata.caption.length > 20) { - return (this.gridPhoto.media).metadata.caption.substring(0, 17) + '...'; - } - return (this.gridPhoto.media).metadata.caption; - } - return this.gridPhoto.media.name; - } - /* onImageLoad() { this.loading.show = false; diff --git a/frontend/app/gallery/navigator/navigator.gallery.component.html b/frontend/app/gallery/navigator/navigator.gallery.component.html index 9a45dee6..c2056bcd 100644 --- a/frontend/app/gallery/navigator/navigator.gallery.component.html +++ b/frontend/app/gallery/navigator/navigator.gallery.component.html @@ -15,6 +15,7 @@ + {{searchResult.searchText}} diff --git a/frontend/app/gallery/search/search.gallery.component.html b/frontend/app/gallery/search/search.gallery.component.html index 40de4780..2c85a978 100644 --- a/frontend/app/gallery/search/search.gallery.component.html +++ b/frontend/app/gallery/search/search.gallery.component.html @@ -25,6 +25,7 @@ + {{item.preText}}{{item.highLightText}}{{item.postText}}