mirror of
https://github.com/bpatrik/pigallery2.git
synced 2024-12-25 02:04:15 +02:00
Implementing distance search #58
This commit is contained in:
parent
cbfbebf697
commit
0f7ac812ea
61
package-lock.json
generated
61
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pigallery2",
|
||||
"version": "1.8.2",
|
||||
"version": "1.8.4",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -3344,6 +3344,15 @@
|
||||
"integrity": "sha512-4nhBPStMK04rruRVtVc6cDqhu7S9GZai0fpXgPXrFpcPX6Xul8xnrjSdGB4KPBVYG/R5+fXWdCM8qBoiULWGPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node-geocoder": {
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-geocoder/-/node-geocoder-3.24.1.tgz",
|
||||
"integrity": "sha512-NpkCX6ooS3Fc6fjVVCWUDcq7/0siDU6zVu+Q29neHLXOGkRc9VmLvLTl3yeaDRatdpAjkD9O7hOY67zrf7WP9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/q": {
|
||||
"version": "0.0.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz",
|
||||
@ -4702,8 +4711,7 @@
|
||||
"bluebird": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz",
|
||||
"integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw=="
|
||||
},
|
||||
"bmp-js": {
|
||||
"version": "0.1.0",
|
||||
@ -13607,6 +13615,11 @@
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz",
|
||||
"integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||
},
|
||||
"node-fetch-npm": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz",
|
||||
@ -13624,6 +13637,17 @@
|
||||
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node-geocoder": {
|
||||
"version": "3.27.0",
|
||||
"resolved": "https://registry.npmjs.org/node-geocoder/-/node-geocoder-3.27.0.tgz",
|
||||
"integrity": "sha512-fNMi9smx56wFhG+2sd0qVsq5RgNlkUuQQ7UWqPwynoMb0GjxSP9joAn8wah4YDv6UzZu3b41cNmd0BglEPkC+Q==",
|
||||
"requires": {
|
||||
"bluebird": "^3.5.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"request": "^2.88.0",
|
||||
"request-promise": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"node-gyp": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
|
||||
@ -16502,6 +16526,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"request-promise": {
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz",
|
||||
"integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==",
|
||||
"requires": {
|
||||
"bluebird": "^3.5.0",
|
||||
"request-promise-core": "1.1.4",
|
||||
"stealthy-require": "^1.1.1",
|
||||
"tough-cookie": "^2.3.3"
|
||||
}
|
||||
},
|
||||
"request-promise-core": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
|
||||
"integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.19"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@ -17840,6 +17890,11 @@
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"stealthy-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
|
||||
},
|
||||
"stream-browserify": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
|
||||
|
@ -43,6 +43,7 @@
|
||||
"image-size": "0.9.3",
|
||||
"jimp": "0.16.1",
|
||||
"locale": "0.1.0",
|
||||
"node-geocoder": "^3.27.0",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"sharp": "0.23.4",
|
||||
"sqlite3": "5.0.0",
|
||||
@ -84,6 +85,7 @@
|
||||
"@types/jasmine": "3.6.2",
|
||||
"@types/jsonwebtoken": "8.5.0",
|
||||
"@types/node": "14.14.19",
|
||||
"@types/node-geocoder": "^3.24.1",
|
||||
"@types/sharp": "0.26.1",
|
||||
"@types/winston": "2.4.4",
|
||||
"@types/xml2js": "0.4.7",
|
||||
|
6
src/backend/exceptions/LocationLookupException.ts
Normal file
6
src/backend/exceptions/LocationLookupException.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export class LocationLookupException extends Error {
|
||||
|
||||
constructor(message: string, public location: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import {Utils} from '../../common/Utils';
|
||||
import {QueryParams} from '../../common/QueryParams';
|
||||
import {VideoProcessing} from '../model/fileprocessing/VideoProcessing';
|
||||
import {SearchQueryDTO, SearchQueryTypes} from '../../common/entities/SearchQueryDTO';
|
||||
import {LocationLookupException} from '../exceptions/LocationLookupException';
|
||||
|
||||
|
||||
export class GalleryMWs {
|
||||
@ -167,6 +168,9 @@ export class GalleryMWs {
|
||||
req.resultPipe = new ContentWrapper(null, result);
|
||||
return next();
|
||||
} catch (err) {
|
||||
if (err instanceof LocationLookupException) {
|
||||
return next(new ErrorDTO(ErrorCodes.LocationLookUp_ERROR, 'Cannot find location: ' + err.location, err));
|
||||
}
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err));
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,6 @@ export class ThumbnailGeneratorMWs {
|
||||
|
||||
|
||||
private static addThInfoTODir(directory: DirectoryDTO) {
|
||||
console.log(directory.path, directory.name);
|
||||
if (typeof directory.media !== 'undefined') {
|
||||
ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media);
|
||||
}
|
||||
|
@ -1,10 +1,30 @@
|
||||
import {GPSMetadata} from '../../../common/entities/PhotoDTO';
|
||||
|
||||
import * as NodeGeocoder from 'node-geocoder';
|
||||
import {LocationLookupException} from '../../exceptions/LocationLookupException';
|
||||
import {Utils} from '../../../common/Utils';
|
||||
|
||||
export class LocationManager {
|
||||
readonly geocoder: NodeGeocoder.Geocoder;
|
||||
cache = new Utils.LRU<GPSMetadata>(100);
|
||||
|
||||
constructor() {
|
||||
this.geocoder = NodeGeocoder({provider: 'openstreetmap'});
|
||||
}
|
||||
|
||||
async getGPSData(text: string): Promise<GPSMetadata> {
|
||||
throw new Error('TODO implement');
|
||||
if (!this.cache.get(text)) {
|
||||
|
||||
const ret = await this.geocoder.geocode(text);
|
||||
if (ret.length < 1) {
|
||||
throw new LocationLookupException('Cannot find location:' + text, text);
|
||||
}
|
||||
this.cache.set(text, {
|
||||
latitude: ret[0].latitude,
|
||||
longitude: ret[0].longitude
|
||||
});
|
||||
}
|
||||
|
||||
return this.cache.get(text);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -236,7 +236,6 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
||||
if (dir.preview) {
|
||||
dir.preview.readyThumbnails = [];
|
||||
dir.preview.readyIcon = false;
|
||||
console.log(dir.preview.directory);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ export class SearchManager implements ISearchManager {
|
||||
.map(r => r.name), SearchQueryTypes.person));
|
||||
}
|
||||
|
||||
if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.position) {
|
||||
if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.position || type === SearchQueryTypes.distance) {
|
||||
(await photoRepository
|
||||
.createQueryBuilder('photo')
|
||||
.select('photo.metadata.positionData.country as country, ' +
|
||||
@ -99,7 +99,8 @@ export class SearchManager implements ISearchManager {
|
||||
.map(pm => <Array<string>>[pm.city || '', pm.country || '', pm.state || ''])
|
||||
.forEach(positions => {
|
||||
result = result.concat(this.encapsulateAutoComplete(positions
|
||||
.filter(p => p.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.position));
|
||||
.filter(p => p.toLowerCase().indexOf(text.toLowerCase()) !== -1),
|
||||
type === SearchQueryTypes.distance ? type : SearchQueryTypes.position));
|
||||
});
|
||||
}
|
||||
|
||||
@ -241,12 +242,18 @@ export class SearchManager implements ISearchManager {
|
||||
latDelta = (1 / ((2 * Math.PI / 360) * earth)), // 1 km in degree
|
||||
lonDelta = (1 / ((2 * Math.PI / 360) * earth)); // 1 km in degree
|
||||
|
||||
const minLat = (<DistanceSearch>query).from.GPSData.latitude - ((<DistanceSearch>query).distance * latDelta),
|
||||
maxLat = (<DistanceSearch>query).from.GPSData.latitude + ((<DistanceSearch>query).distance * latDelta),
|
||||
minLon = (<DistanceSearch>query).from.GPSData.latitude -
|
||||
((<DistanceSearch>query).distance * lonDelta) / Math.cos(minLat * (Math.PI / 180)),
|
||||
maxLon = (<DistanceSearch>query).from.GPSData.latitude +
|
||||
((<DistanceSearch>query).distance * lonDelta) / Math.cos(maxLat * (Math.PI / 180));
|
||||
// TODO: properly handle latitude / longitude boundaries
|
||||
const trimRange = (value: number, min: number, max: number) => {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
};
|
||||
|
||||
const minLat = trimRange((<DistanceSearch>query).from.GPSData.latitude - ((<DistanceSearch>query).distance * latDelta), -90, 90),
|
||||
maxLat = trimRange((<DistanceSearch>query).from.GPSData.latitude + ((<DistanceSearch>query).distance * latDelta), -90, 90),
|
||||
minLon = trimRange((<DistanceSearch>query).from.GPSData.longitude -
|
||||
((<DistanceSearch>query).distance * lonDelta) / Math.cos(minLat * (Math.PI / 180)), -180, 180),
|
||||
maxLon = trimRange((<DistanceSearch>query).from.GPSData.longitude +
|
||||
((<DistanceSearch>query).distance * lonDelta) / Math.cos(maxLat * (Math.PI / 180)), -180, 180);
|
||||
|
||||
|
||||
return new Brackets(q => {
|
||||
const textParam: any = {};
|
||||
|
@ -146,7 +146,7 @@ export class SearchQueryParser {
|
||||
this.keywords.someOf + ':' :
|
||||
new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').exec(str)[0];
|
||||
let tmpList: any = this.parse(str.slice(prefix.length + 1, -1), false); // trim brackets
|
||||
// console.log(JSON.stringify(tmpList, null, 4));
|
||||
|
||||
const unfoldList = (q: SearchListQuery): SearchQueryDTO[] => {
|
||||
if (q.list) {
|
||||
if (q.type === SearchQueryTypes.UNKNOWN_RELATION) {
|
||||
@ -207,7 +207,8 @@ export class SearchQueryParser {
|
||||
}
|
||||
if (new RegExp('^\\d*-' + this.keywords.kmFrom + ':').test(str)) {
|
||||
let from = str.slice(new RegExp('^\\d*-' + this.keywords.kmFrom + ':').exec(str)[0].length);
|
||||
if (from.charAt(0) === '(' && from.charAt(from.length - 1) === ')') {
|
||||
if ((from.charAt(0) === '(' && from.charAt(from.length - 1) === ')') ||
|
||||
(from.charAt(0) === '"' && from.charAt(from.length - 1) === '"')) {
|
||||
from = from.slice(1, from.length - 1);
|
||||
}
|
||||
return <DistanceSearch>{
|
||||
|
@ -268,3 +268,35 @@ export class Utils {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Utils {
|
||||
export class LRU<V> {
|
||||
data: { [key: string]: { value: V, usage: number } } = {};
|
||||
|
||||
|
||||
constructor(public readonly size: number) {
|
||||
}
|
||||
|
||||
set(key: string, value: V): void {
|
||||
this.data[key] = {usage: Date.now(), value: value};
|
||||
if (Object.keys(this.data).length > this.size) {
|
||||
let oldestK = key;
|
||||
let oldest = this.data[oldestK].usage;
|
||||
for (const k in this.data) {
|
||||
if (this.data[k].usage < oldest) {
|
||||
oldestK = k;
|
||||
oldest = this.data[oldestK].usage;
|
||||
}
|
||||
}
|
||||
delete this.data[oldestK];
|
||||
}
|
||||
}
|
||||
|
||||
get(key: string): V {
|
||||
if (!this.data[key]) {
|
||||
return;
|
||||
}
|
||||
return this.data[key].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,8 @@ export enum ErrorCodes {
|
||||
|
||||
SETTINGS_ERROR = 13,
|
||||
TASK_ERROR = 14,
|
||||
JOB_ERROR = 15
|
||||
JOB_ERROR = 15,
|
||||
LocationLookUp_ERROR = 16,
|
||||
}
|
||||
|
||||
export class ErrorDTO {
|
||||
|
@ -27,40 +27,55 @@
|
||||
</ng-container>
|
||||
|
||||
|
||||
<div body class="container-fluid" style="width: 100%; padding:0" *ngIf="_galleryService.content.value.directory">
|
||||
<app-gallery-navbar [directory]="_galleryService.content.value.directory"></app-gallery-navbar>
|
||||
<div body class="container-fluid" style="width: 100%; padding:0">
|
||||
<ng-container *ngIf="_galleryService.content.value.error">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{{_galleryService.content.value.error}}
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!_galleryService.content.value.error">
|
||||
<ng-container *ngIf="_galleryService.content.value.directory">
|
||||
|
||||
<app-gallery-directories class="directories" [directories]="directories"></app-gallery-directories>
|
||||
<app-gallery-navbar [directory]="_galleryService.content.value.directory"></app-gallery-navbar>
|
||||
|
||||
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
|
||||
[photos]="_galleryService.content.value.directory.media"
|
||||
[gpxFiles]="_galleryService.content.value.directory.metaFile | gpxFiles"></app-gallery-map>
|
||||
<app-gallery-grid [media]="_galleryService.content.value.directory.media"
|
||||
[lightbox]="lightbox"></app-gallery-grid>
|
||||
</div>
|
||||
<!-- Search-->
|
||||
<div body class="container-fluid" style="width: 100%; padding:0" *ngIf="_galleryService.content.value.searchResult">
|
||||
<div class="alert alert-info" role="alert"
|
||||
*ngIf="_galleryService.content.value.searchResult.resultOverflow == true" i18n>
|
||||
Too many results to show. Refine your search.
|
||||
</div>
|
||||
<app-gallery-navbar [searchResult]="_galleryService.content.value.searchResult"></app-gallery-navbar>
|
||||
<app-gallery-directories class="directories" [directories]="directories"></app-gallery-directories>
|
||||
|
||||
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
|
||||
[photos]="_galleryService.content.value.searchResult.media"
|
||||
[gpxFiles]="_galleryService.content.value.searchResult.metaFile | gpxFiles"></app-gallery-map>
|
||||
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
|
||||
[photos]="_galleryService.content.value.directory.media"
|
||||
[gpxFiles]="_galleryService.content.value.directory.metaFile | gpxFiles"></app-gallery-map>
|
||||
<app-gallery-grid [media]="_galleryService.content.value.directory.media"
|
||||
[lightbox]="lightbox"></app-gallery-grid>
|
||||
|
||||
<app-gallery-directories class="directories" [directories]="directories"></app-gallery-directories>
|
||||
|
||||
<app-gallery-grid [media]="_galleryService.content.value.searchResult.media"
|
||||
[lightbox]="lightbox"></app-gallery-grid>
|
||||
</ng-container>
|
||||
<!-- Search-->
|
||||
<ng-container *ngIf="_galleryService.content.value.searchResult">
|
||||
<div class="alert alert-info" role="alert"
|
||||
*ngIf="_galleryService.content.value.searchResult.resultOverflow == true" i18n>
|
||||
Too many results to show. Refine your search.
|
||||
</div>
|
||||
<app-gallery-navbar [searchResult]="_galleryService.content.value.searchResult"></app-gallery-navbar>
|
||||
|
||||
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
|
||||
[photos]="_galleryService.content.value.searchResult.media"
|
||||
[gpxFiles]="_galleryService.content.value.searchResult.metaFile | gpxFiles"></app-gallery-map>
|
||||
|
||||
<app-gallery-directories class="directories" [directories]="directories"></app-gallery-directories>
|
||||
|
||||
<app-gallery-grid [media]="_galleryService.content.value.searchResult.media"
|
||||
[lightbox]="lightbox"></app-gallery-grid>
|
||||
|
||||
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div body class="container"
|
||||
style="width: 100%; padding:0"
|
||||
*ngIf="(!_galleryService.content.value.directory ||
|
||||
_galleryService.content.value.directory.isPartial == true)
|
||||
&& !_galleryService.content.value.searchResult">
|
||||
&& !_galleryService.content.value.searchResult
|
||||
&& !_galleryService.content.value.error">
|
||||
<div class="spinner">
|
||||
|
||||
</div>
|
||||
|
@ -11,12 +11,13 @@ import {SortingMethods} from '../../../../common/entities/SortingMethods';
|
||||
import {QueryParams} from '../../../../common/QueryParams';
|
||||
import {PG2ConfMap} from '../../../../common/PG2ConfMap';
|
||||
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
|
||||
import {ErrorCodes} from '../../../../common/entities/Error';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class GalleryService {
|
||||
|
||||
public content: BehaviorSubject<ContentWrapper>;
|
||||
public content: BehaviorSubject<ContentWrapperWithError>;
|
||||
public sorting: BehaviorSubject<SortingMethods>;
|
||||
lastRequest: { directory: string } = {
|
||||
directory: null
|
||||
@ -29,7 +30,7 @@ export class GalleryService {
|
||||
private galleryCacheService: GalleryCacheService,
|
||||
private _shareService: ShareService,
|
||||
private navigationService: NavigationService) {
|
||||
this.content = new BehaviorSubject<ContentWrapper>(new ContentWrapper());
|
||||
this.content = new BehaviorSubject<ContentWrapperWithError>(new ContentWrapperWithError());
|
||||
this.sorting = new BehaviorSubject<SortingMethods>(Config.Client.Other.defaultPhotoSortingMethod);
|
||||
}
|
||||
|
||||
@ -56,7 +57,7 @@ export class GalleryService {
|
||||
}
|
||||
|
||||
|
||||
setContent(content: ContentWrapper): void {
|
||||
setContent(content: ContentWrapperWithError): void {
|
||||
this.content.next(content);
|
||||
if (content.directory) {
|
||||
const sort = this.galleryCacheService.getSorting(content.directory);
|
||||
@ -70,7 +71,7 @@ export class GalleryService {
|
||||
|
||||
|
||||
public async loadDirectory(directoryName: string): Promise<void> {
|
||||
const content = new ContentWrapper();
|
||||
const content = new ContentWrapperWithError();
|
||||
|
||||
content.directory = this.galleryCacheService.getDirectory(directoryName);
|
||||
content.searchResult = null;
|
||||
@ -93,7 +94,7 @@ export class GalleryService {
|
||||
}
|
||||
|
||||
try {
|
||||
const cw = await this.networkService.getJson<ContentWrapper>('/gallery/content/' + directoryName, params);
|
||||
const cw = await this.networkService.getJson<ContentWrapperWithError>('/gallery/content/' + directoryName, params);
|
||||
|
||||
|
||||
if (!cw || cw.notModified === true) {
|
||||
@ -124,12 +125,20 @@ export class GalleryService {
|
||||
this.ongoingSearch = query;
|
||||
|
||||
|
||||
this.setContent(new ContentWrapper());
|
||||
const cw = new ContentWrapper();
|
||||
this.setContent(new ContentWrapperWithError());
|
||||
const cw = new ContentWrapperWithError();
|
||||
cw.searchResult = this.galleryCacheService.getSearch(query);
|
||||
if (cw.searchResult == null) {
|
||||
cw.searchResult = (await this.networkService.getJson<ContentWrapper>('/search/' + query)).searchResult;
|
||||
this.galleryCacheService.setSearch(query, cw.searchResult);
|
||||
try {
|
||||
cw.searchResult = (await this.networkService.getJson<ContentWrapper>('/search/' + query)).searchResult;
|
||||
this.galleryCacheService.setSearch(query, cw.searchResult);
|
||||
} catch (e) {
|
||||
if (e.code === ErrorCodes.LocationLookUp_ERROR) {
|
||||
cw.error = 'Cannot find location: ' + e.message;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ongoingSearch !== query) {
|
||||
@ -146,3 +155,8 @@ export class GalleryService {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class ContentWrapperWithError extends ContentWrapper {
|
||||
public error: string;
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import {SearchQueryParser} from '../../../../../common/SearchQueryParser';
|
||||
export class AutoCompleteService {
|
||||
|
||||
private keywords: string[] = [];
|
||||
private relationKeywords: string[] = [];
|
||||
private textSearchKeywordsMap: { [key: string]: SearchQueryTypes } = {};
|
||||
|
||||
constructor(private _networkService: NetworkService,
|
||||
@ -29,7 +28,11 @@ export class AutoCompleteService {
|
||||
this.keywords.push(this._searchQueryParserService.keywords.and);
|
||||
this.keywords.push(this._searchQueryParserService.keywords.or);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.keywords.push(i + this._searchQueryParserService.keywords.NSomeOf);
|
||||
this.keywords.push(i + '-' + this._searchQueryParserService.keywords.NSomeOf + ':( )');
|
||||
}
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.keywords.push(i + '-' + this._searchQueryParserService.keywords.kmFrom + ':');
|
||||
}
|
||||
|
||||
this.keywords.push(this._searchQueryParserService.keywords.to + ':' +
|
||||
@ -55,11 +58,11 @@ export class AutoCompleteService {
|
||||
if (searchText === '') {
|
||||
return items;
|
||||
}
|
||||
this.typedAutoComplete(searchText, type, items);
|
||||
this.typedAutoComplete(searchText, text.current, type, items);
|
||||
return items;
|
||||
}
|
||||
|
||||
public typedAutoComplete(text: string, type: SearchQueryTypes,
|
||||
public typedAutoComplete(text: string, fullText: string, type: SearchQueryTypes,
|
||||
items?: BehaviorSubject<RenderableAutoCompleteItem[]>): BehaviorSubject<RenderableAutoCompleteItem[]> {
|
||||
items = items || new BehaviorSubject([]);
|
||||
|
||||
@ -71,10 +74,10 @@ export class AutoCompleteService {
|
||||
}
|
||||
this._networkService.getJson<IAutoCompleteItem[]>('/autocomplete/' + text, acParams).then(ret => {
|
||||
this._galleryCacheService.setAutoComplete(text, type, ret);
|
||||
items.next(this.sortResults(text, ret.map(i => this.ACItemToRenderable(i)).concat(items.value)));
|
||||
items.next(this.sortResults(text, ret.map(i => this.ACItemToRenderable(i, fullText)).concat(items.value)));
|
||||
});
|
||||
} else {
|
||||
items.next(this.sortResults(text, cached.map(i => this.ACItemToRenderable(i)).concat(items.value)));
|
||||
items.next(this.sortResults(text, cached.map(i => this.ACItemToRenderable(i, fullText)).concat(items.value)));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
@ -91,23 +94,37 @@ export class AutoCompleteService {
|
||||
return tokens[1];
|
||||
}
|
||||
|
||||
|
||||
private getTypeFromPrefix(text: string): SearchQueryTypes {
|
||||
const tokens = text.split(':');
|
||||
if (tokens.length !== 2) {
|
||||
return null;
|
||||
}
|
||||
if (new RegExp('^\\d*-' + this._searchQueryParserService.keywords.kmFrom).test(tokens[0])) {
|
||||
return SearchQueryTypes.distance;
|
||||
}
|
||||
return this.textSearchKeywordsMap[tokens[0]] || null;
|
||||
}
|
||||
|
||||
private ACItemToRenderable(item: IAutoCompleteItem): RenderableAutoCompleteItem {
|
||||
private ACItemToRenderable(item: IAutoCompleteItem, searchToken: string): RenderableAutoCompleteItem {
|
||||
if (!item.type) {
|
||||
return {text: item.text, queryHint: item.text};
|
||||
}
|
||||
if (TextSearchQueryTypes.includes(item.type) && item.type !== SearchQueryTypes.any_text) {
|
||||
if ((TextSearchQueryTypes.includes(item.type) ||
|
||||
item.type === SearchQueryTypes.distance) &&
|
||||
item.type !== SearchQueryTypes.any_text) {
|
||||
let queryHint = (<any>this._searchQueryParserService.keywords)[SearchQueryTypes[item.type]] + ':"' + item.text + '"';
|
||||
|
||||
// if its a distance search, change hint text
|
||||
const tokens = searchToken.split(':');
|
||||
if (tokens.length === 2 &&
|
||||
new RegExp('^\\d*-' + this._searchQueryParserService.keywords.kmFrom).test(tokens[0])) {
|
||||
queryHint = tokens[0] + ':"' + item.text + '"';
|
||||
}
|
||||
|
||||
return {
|
||||
text: item.text, type: item.type,
|
||||
queryHint:
|
||||
(<any>this._searchQueryParserService.keywords)[SearchQueryTypes[item.type]] + ':"' + item.text + '"'
|
||||
queryHint: queryHint
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
@ -34,7 +34,7 @@
|
||||
<div class="autocomplete-list" *ngIf="autoCompleteRenders.length > 0"
|
||||
(mouseover)="setMouseOverAutoComplete(true)" (mouseout)="setMouseOverAutoComplete(false)">
|
||||
<div class="autocomplete-item"
|
||||
[ngClass]="{'autocomplete-item-selected':highlightedAutoCompleteItem == i}"
|
||||
[ngClass]="{'autocomplete-item-selected': highlightedAutoCompleteItem === i}"
|
||||
(mouseover)="setMouseOverAutoCompleteItem(i)"
|
||||
(click)="searchAutoComplete(item)"
|
||||
*ngFor="let item of autoCompleteRenders; let i = index">
|
||||
@ -46,6 +46,7 @@
|
||||
<span *ngSwitchCase="SearchQueryTypes.keyword" class="oi oi-tag"></span>
|
||||
<span *ngSwitchCase="SearchQueryTypes.person" class="oi oi-person"></span>
|
||||
<span *ngSwitchCase="SearchQueryTypes.position" class="oi oi-map-marker"></span>
|
||||
<span *ngSwitchCase="SearchQueryTypes.distance" class="oi oi-map-marker"></span>
|
||||
</span>
|
||||
{{item.preText}}<strong>{{item.highLightText}}</strong>{{item.postText}}
|
||||
</div>
|
||||
|
@ -62,7 +62,6 @@ describe('GalleryRouter', (sqlHelper: DBTestHelper) => {
|
||||
expect(result.body.error).to.be.equal(null);
|
||||
expect(result.body.result).to.not.be.equal(null);
|
||||
expect(result.body.result.directory).to.not.be.equal(null);
|
||||
console.log(result.body.result.directory);
|
||||
});
|
||||
|
||||
it('should load gallery twice (to force loading form db)', async () => {
|
||||
|
@ -999,7 +999,7 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
|
||||
|
||||
});
|
||||
|
||||
it('should search distance', async () => {
|
||||
it('should search distance', async () => {
|
||||
ObjectManagers.getInstance().LocationManager = new LocationManager();
|
||||
const sm = new SearchManager();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user