mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +08:00
improving advanced search
This commit is contained in:
parent
9a923aa8ab
commit
e643ee2ed1
@ -15,17 +15,19 @@ import {
|
|||||||
DateSearch,
|
DateSearch,
|
||||||
DistanceSearch,
|
DistanceSearch,
|
||||||
OrientationSearch,
|
OrientationSearch,
|
||||||
OrientationSearchTypes,
|
|
||||||
ORSearchQuery,
|
ORSearchQuery,
|
||||||
RatingSearch,
|
RatingSearch,
|
||||||
ResolutionSearch,
|
ResolutionSearch,
|
||||||
|
SearchListQuery,
|
||||||
SearchQueryDTO,
|
SearchQueryDTO,
|
||||||
SearchQueryTypes,
|
SearchQueryTypes,
|
||||||
|
SomeOfSearchQuery,
|
||||||
TextSearch,
|
TextSearch,
|
||||||
TextSearchQueryTypes
|
TextSearchQueryTypes
|
||||||
} from '../../../../common/entities/SearchQueryDTO';
|
} from '../../../../common/entities/SearchQueryDTO';
|
||||||
import {GalleryManager} from './GalleryManager';
|
import {GalleryManager} from './GalleryManager';
|
||||||
import {ObjectManagers} from '../../ObjectManagers';
|
import {ObjectManagers} from '../../ObjectManagers';
|
||||||
|
import {Utils} from '../../../../common/Utils';
|
||||||
|
|
||||||
export class SearchManager implements ISearchManager {
|
export class SearchManager implements ISearchManager {
|
||||||
|
|
||||||
@ -145,6 +147,7 @@ export class SearchManager implements ISearchManager {
|
|||||||
|
|
||||||
async aSearch(query: SearchQueryDTO) {
|
async aSearch(query: SearchQueryDTO) {
|
||||||
query = this.flattenSameOfQueries(query);
|
query = this.flattenSameOfQueries(query);
|
||||||
|
console.log(JSON.stringify(query, null, 4));
|
||||||
query = await this.getGPSData(query);
|
query = await this.getGPSData(query);
|
||||||
const connection = await SQLConnection.getConnection();
|
const connection = await SQLConnection.getConnection();
|
||||||
|
|
||||||
@ -367,10 +370,17 @@ export class SearchManager implements ISearchManager {
|
|||||||
textParam['minLat' + paramCounter.value] = minLat;
|
textParam['minLat' + paramCounter.value] = minLat;
|
||||||
textParam['maxLon' + paramCounter.value] = maxLon;
|
textParam['maxLon' + paramCounter.value] = maxLon;
|
||||||
textParam['minLon' + paramCounter.value] = minLon;
|
textParam['minLon' + paramCounter.value] = minLon;
|
||||||
|
if (!(<DistanceSearch>query).negate) {
|
||||||
q.where(`media.metadata.positionData.GPSData.latitude < :maxLat${paramCounter.value}`, textParam);
|
q.where(`media.metadata.positionData.GPSData.latitude < :maxLat${paramCounter.value}`, textParam);
|
||||||
q.andWhere(`media.metadata.positionData.GPSData.latitude > :minLat${paramCounter.value}`, textParam);
|
q.andWhere(`media.metadata.positionData.GPSData.latitude > :minLat${paramCounter.value}`, textParam);
|
||||||
q.andWhere(`media.metadata.positionData.GPSData.longitude < :maxLon${paramCounter.value}`, textParam);
|
q.andWhere(`media.metadata.positionData.GPSData.longitude < :maxLon${paramCounter.value}`, textParam);
|
||||||
q.andWhere(`media.metadata.positionData.GPSData.longitude > :minLon${paramCounter.value}`, textParam);
|
q.andWhere(`media.metadata.positionData.GPSData.longitude > :minLon${paramCounter.value}`, textParam);
|
||||||
|
} else {
|
||||||
|
q.where(`media.metadata.positionData.GPSData.latitude > :maxLat${paramCounter.value}`, textParam);
|
||||||
|
q.orWhere(`media.metadata.positionData.GPSData.latitude < :minLat${paramCounter.value}`, textParam);
|
||||||
|
q.orWhere(`media.metadata.positionData.GPSData.longitude > :maxLon${paramCounter.value}`, textParam);
|
||||||
|
q.orWhere(`media.metadata.positionData.GPSData.longitude < :minLon${paramCounter.value}`, textParam);
|
||||||
|
}
|
||||||
return q;
|
return q;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -379,17 +389,22 @@ export class SearchManager implements ISearchManager {
|
|||||||
if (typeof (<DateSearch>query).before === 'undefined' && typeof (<DateSearch>query).after === 'undefined') {
|
if (typeof (<DateSearch>query).before === 'undefined' && typeof (<DateSearch>query).after === 'undefined') {
|
||||||
throw new Error('Invalid search query: Date Query should contain before or after value');
|
throw new Error('Invalid search query: Date Query should contain before or after value');
|
||||||
}
|
}
|
||||||
if (typeof (<DateSearch>query).before !== 'undefined') {
|
const whereFN = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
|
||||||
const textParam: any = {};
|
const relation = (<TextSearch>query).negate ? '<' : '>=';
|
||||||
textParam['before' + paramCounter.value] = (<DateSearch>query).before;
|
const relationRev = (<TextSearch>query).negate ? '>' : '<=';
|
||||||
q.where(`media.metadata.creationDate <= :before${paramCounter.value}`, textParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof (<DateSearch>query).after !== 'undefined') {
|
if (typeof (<DateSearch>query).after !== 'undefined') {
|
||||||
const textParam: any = {};
|
const textParam: any = {};
|
||||||
textParam['after' + paramCounter.value] = (<DateSearch>query).after;
|
textParam['after' + paramCounter.value] = (<DateSearch>query).after;
|
||||||
q.andWhere(`media.metadata.creationDate >= :after${paramCounter.value}`, textParam);
|
q.where(`media.metadata.creationDate ${relation} :after${paramCounter.value}`, textParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof (<DateSearch>query).before !== 'undefined') {
|
||||||
|
const textParam: any = {};
|
||||||
|
textParam['before' + paramCounter.value] = (<DateSearch>query).before;
|
||||||
|
q[whereFN](`media.metadata.creationDate ${relationRev} :before${paramCounter.value}`, textParam);
|
||||||
|
}
|
||||||
|
|
||||||
paramCounter.value++;
|
paramCounter.value++;
|
||||||
return q;
|
return q;
|
||||||
});
|
});
|
||||||
@ -399,16 +414,20 @@ export class SearchManager implements ISearchManager {
|
|||||||
if (typeof (<RatingSearch>query).min === 'undefined' && typeof (<RatingSearch>query).max === 'undefined') {
|
if (typeof (<RatingSearch>query).min === 'undefined' && typeof (<RatingSearch>query).max === 'undefined') {
|
||||||
throw new Error('Invalid search query: Rating Query should contain min or max value');
|
throw new Error('Invalid search query: Rating Query should contain min or max value');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const whereFN = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
|
||||||
|
const relation = (<TextSearch>query).negate ? '<' : '>=';
|
||||||
|
const relationRev = (<TextSearch>query).negate ? '>' : '<=';
|
||||||
if (typeof (<RatingSearch>query).min !== 'undefined') {
|
if (typeof (<RatingSearch>query).min !== 'undefined') {
|
||||||
const textParam: any = {};
|
const textParam: any = {};
|
||||||
textParam['min' + paramCounter.value] = (<RatingSearch>query).min;
|
textParam['min' + paramCounter.value] = (<RatingSearch>query).min;
|
||||||
q.where(`media.metadata.rating >= :min${paramCounter.value}`, textParam);
|
q.where(`media.metadata.rating ${relation} :min${paramCounter.value}`, textParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof (<RatingSearch>query).max !== 'undefined') {
|
if (typeof (<RatingSearch>query).max !== 'undefined') {
|
||||||
const textParam: any = {};
|
const textParam: any = {};
|
||||||
textParam['max' + paramCounter.value] = (<RatingSearch>query).max;
|
textParam['max' + paramCounter.value] = (<RatingSearch>query).max;
|
||||||
q.andWhere(`media.metadata.rating <= :max${paramCounter.value}`, textParam);
|
q[whereFN](`media.metadata.rating ${relationRev} :max${paramCounter.value}`, textParam);
|
||||||
}
|
}
|
||||||
paramCounter.value++;
|
paramCounter.value++;
|
||||||
return q;
|
return q;
|
||||||
@ -419,16 +438,20 @@ export class SearchManager implements ISearchManager {
|
|||||||
if (typeof (<ResolutionSearch>query).min === 'undefined' && typeof (<ResolutionSearch>query).max === 'undefined') {
|
if (typeof (<ResolutionSearch>query).min === 'undefined' && typeof (<ResolutionSearch>query).max === 'undefined') {
|
||||||
throw new Error('Invalid search query: Rating Query should contain min or max value');
|
throw new Error('Invalid search query: Rating Query should contain min or max value');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const whereFN = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
|
||||||
|
const relation = (<TextSearch>query).negate ? '<' : '>=';
|
||||||
|
const relationRev = (<TextSearch>query).negate ? '>' : '<=';
|
||||||
if (typeof (<ResolutionSearch>query).min !== 'undefined') {
|
if (typeof (<ResolutionSearch>query).min !== 'undefined') {
|
||||||
const textParam: any = {};
|
const textParam: any = {};
|
||||||
textParam['min' + paramCounter.value] = (<RatingSearch>query).min * 1000 * 1000;
|
textParam['min' + paramCounter.value] = (<RatingSearch>query).min * 1000 * 1000;
|
||||||
q.where(`media.metadata.size.width * media.metadata.size.height >= :min${paramCounter.value}`, textParam);
|
q.where(`media.metadata.size.width * media.metadata.size.height ${relation} :min${paramCounter.value}`, textParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof (<ResolutionSearch>query).max !== 'undefined') {
|
if (typeof (<ResolutionSearch>query).max !== 'undefined') {
|
||||||
const textParam: any = {};
|
const textParam: any = {};
|
||||||
textParam['max' + paramCounter.value] = (<RatingSearch>query).max * 1000 * 1000;
|
textParam['max' + paramCounter.value] = (<RatingSearch>query).max * 1000 * 1000;
|
||||||
q.andWhere(`media.metadata.size.width * media.metadata.size.height <= :max${paramCounter.value}`, textParam);
|
q[whereFN](`media.metadata.size.width * media.metadata.size.height ${relationRev} :max${paramCounter.value}`, textParam);
|
||||||
}
|
}
|
||||||
paramCounter.value++;
|
paramCounter.value++;
|
||||||
return q;
|
return q;
|
||||||
@ -436,11 +459,10 @@ export class SearchManager implements ISearchManager {
|
|||||||
|
|
||||||
case SearchQueryTypes.orientation:
|
case SearchQueryTypes.orientation:
|
||||||
return new Brackets(q => {
|
return new Brackets(q => {
|
||||||
if ((<OrientationSearch>query).orientation === OrientationSearchTypes.landscape) {
|
if ((<OrientationSearch>query).landscape) {
|
||||||
q.where('media.metadata.size.width >= media.metadata.size.height');
|
q.where('media.metadata.size.width >= media.metadata.size.height');
|
||||||
}
|
} else {
|
||||||
if ((<OrientationSearch>query).orientation === OrientationSearchTypes.portrait) {
|
q.where('media.metadata.size.width <= media.metadata.size.height');
|
||||||
q.andWhere('media.metadata.size.width <= media.metadata.size.height');
|
|
||||||
}
|
}
|
||||||
paramCounter.value++;
|
paramCounter.value++;
|
||||||
return q;
|
return q;
|
||||||
@ -458,6 +480,11 @@ export class SearchManager implements ISearchManager {
|
|||||||
return (<TextSearch>query).matchType === TextSearchQueryTypes.exact_match ? str : `%${str}%`;
|
return (<TextSearch>query).matchType === TextSearchQueryTypes.exact_match ? str : `%${str}%`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LIKE = (<TextSearch>query).negate ? 'NOT LIKE' : 'LIKE';
|
||||||
|
// if the expression is negated, we use AND instead of OR as nowhere should that match
|
||||||
|
const whereFN = (<TextSearch>query).negate ? 'andWhere' : 'orWhere';
|
||||||
|
const whereFNRev = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
|
||||||
|
|
||||||
const textParam: any = {};
|
const textParam: any = {};
|
||||||
paramCounter.value++;
|
paramCounter.value++;
|
||||||
textParam['text' + paramCounter.value] = createMatchString((<TextSearch>query).text);
|
textParam['text' + paramCounter.value] = createMatchString((<TextSearch>query).text);
|
||||||
@ -468,17 +495,17 @@ export class SearchManager implements ISearchManager {
|
|||||||
|
|
||||||
|
|
||||||
textParam['fullPath' + paramCounter.value] = createMatchString(dirPathStr);
|
textParam['fullPath' + paramCounter.value] = createMatchString(dirPathStr);
|
||||||
q.orWhere(`directory.path LIKE :fullPath${paramCounter.value} COLLATE utf8_general_ci`,
|
q[whereFN](`directory.path ${LIKE} :fullPath${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
textParam);
|
textParam);
|
||||||
|
|
||||||
const directoryPath = GalleryManager.parseRelativeDirePath(dirPathStr);
|
const directoryPath = GalleryManager.parseRelativeDirePath(dirPathStr);
|
||||||
q.orWhere(new Brackets(dq => {
|
q[whereFN](new Brackets(dq => {
|
||||||
textParam['dirName' + paramCounter.value] = createMatchString(directoryPath.name);
|
textParam['dirName' + paramCounter.value] = createMatchString(directoryPath.name);
|
||||||
dq.where(`directory.name LIKE :dirName${paramCounter.value} COLLATE utf8_general_ci`,
|
dq[whereFNRev](`directory.name ${LIKE} :dirName${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
textParam);
|
textParam);
|
||||||
if (dirPathStr.includes('/')) {
|
if (dirPathStr.includes('/')) {
|
||||||
textParam['parentName' + paramCounter.value] = createMatchString(directoryPath.parent);
|
textParam['parentName' + paramCounter.value] = createMatchString(directoryPath.parent);
|
||||||
dq.andWhere(`directory.path LIKE :parentName${paramCounter.value} COLLATE utf8_general_ci`,
|
dq[whereFNRev](`directory.path ${LIKE} :parentName${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
textParam);
|
textParam);
|
||||||
}
|
}
|
||||||
return dq;
|
return dq;
|
||||||
@ -486,29 +513,39 @@ export class SearchManager implements ISearchManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.file_name) {
|
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.file_name) {
|
||||||
q.orWhere(`media.name LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
q[whereFN](`media.name ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
textParam);
|
textParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.caption) {
|
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.caption) {
|
||||||
q.orWhere(`media.metadata.caption LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
q[whereFN](`media.metadata.caption ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
textParam);
|
textParam);
|
||||||
}
|
}
|
||||||
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.person) {
|
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.person) {
|
||||||
q.orWhere(`person.name LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
if (!(<TextSearch>query).negate) {
|
||||||
|
q[whereFN](`person.name ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
textParam);
|
textParam);
|
||||||
|
} else {
|
||||||
|
// because of the Left JOIN on the faces, we also need to check for NULL
|
||||||
|
q[whereFN](new Brackets(dq => {
|
||||||
|
dq.where(`person.name ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
|
textParam);
|
||||||
|
dq.orWhere(`person.name is NULL`);
|
||||||
|
return dq;
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.position) {
|
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.position) {
|
||||||
q.orWhere(`media.metadata.positionData.country LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
q[whereFN](`media.metadata.positionData.country ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
textParam)
|
textParam)
|
||||||
.orWhere(`media.metadata.positionData.state LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
[whereFN](`media.metadata.positionData.state ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
textParam)
|
textParam)
|
||||||
.orWhere(`media.metadata.positionData.city LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
[whereFN](`media.metadata.positionData.city ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
textParam);
|
textParam);
|
||||||
}
|
}
|
||||||
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.keyword) {
|
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.keyword) {
|
||||||
q.orWhere(`media.metadata.keywords LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
|
q[whereFN](`media.metadata.keywords ${LIKE} :text${paramCounter.value} COLLATE utf8_general_ci`,
|
||||||
textParam);
|
textParam);
|
||||||
}
|
}
|
||||||
return q;
|
return q;
|
||||||
@ -516,6 +553,41 @@ export class SearchManager implements ISearchManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private flattenSameOfQueries(query: SearchQueryDTO): SearchQueryDTO {
|
private flattenSameOfQueries(query: SearchQueryDTO): SearchQueryDTO {
|
||||||
|
switch (query.type) {
|
||||||
|
case SearchQueryTypes.AND:
|
||||||
|
case SearchQueryTypes.OR:
|
||||||
|
return <SearchListQuery>{
|
||||||
|
type: query.type,
|
||||||
|
list: (<SearchListQuery>query).list.map(q => this.flattenSameOfQueries(q))
|
||||||
|
};
|
||||||
|
case SearchQueryTypes.SOME_OF:
|
||||||
|
const someOfQ = <SomeOfSearchQuery>query;
|
||||||
|
someOfQ.min = someOfQ.min || 1;
|
||||||
|
|
||||||
|
if (someOfQ.min === 1) {
|
||||||
|
return this.flattenSameOfQueries(<ORSearchQuery>{
|
||||||
|
type: SearchQueryTypes.OR,
|
||||||
|
list: (<SearchListQuery>someOfQ).list
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (someOfQ.min === (<SearchListQuery>query).list.length) {
|
||||||
|
return this.flattenSameOfQueries(<ANDSearchQuery>{
|
||||||
|
type: SearchQueryTypes.AND,
|
||||||
|
list: (<SearchListQuery>someOfQ).list
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const combinations: SearchQueryDTO[][] = Utils.getAnyX(someOfQ.min, (<SearchListQuery>query).list);
|
||||||
|
|
||||||
|
|
||||||
|
return this.flattenSameOfQueries(<ORSearchQuery>{
|
||||||
|
type: SearchQueryTypes.OR,
|
||||||
|
list: combinations.map(c => <ANDSearchQuery>{
|
||||||
|
type: SearchQueryTypes.AND, list: c
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,4 +240,24 @@ export class Utils {
|
|||||||
(value <= 3.402823466 * E && value >= 1.175494351 * nE));
|
(value <= 3.402823466 * E && value >= 1.175494351 * nE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static getAnyX(num: number, arr: any[], start = 0): any[][] {
|
||||||
|
if (num <= 0 || num > arr.length || start >= arr.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (num <= 1) {
|
||||||
|
return arr.slice(start).map(e => [e]);
|
||||||
|
}
|
||||||
|
if (num === arr.length - start) {
|
||||||
|
return [arr.slice(start)];
|
||||||
|
}
|
||||||
|
const ret: any[][] = [];
|
||||||
|
for (let i = start; i < arr.length; ++i) {
|
||||||
|
Utils.getAnyX(num - 1, arr, i + 1).forEach(a => {
|
||||||
|
a.push(arr[i]);
|
||||||
|
ret.push(a);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,30 +24,47 @@ export enum TextSearchQueryTypes {
|
|||||||
exact_match = 1, like = 2
|
exact_match = 1, like = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrientationSearchTypes {
|
|
||||||
portrait = 1, landscape = 2
|
export namespace SearchQueryDTO {
|
||||||
|
export const negate = (query: SearchQueryDTO): SearchQueryDTO => {
|
||||||
|
switch (query.type) {
|
||||||
|
case SearchQueryTypes.AND:
|
||||||
|
query.type = SearchQueryTypes.OR;
|
||||||
|
(<SearchListQuery>query).list = (<SearchListQuery>query).list.map(q => SearchQueryDTO.negate(q));
|
||||||
|
return query;
|
||||||
|
case SearchQueryTypes.OR:
|
||||||
|
query.type = SearchQueryTypes.AND;
|
||||||
|
(<SearchListQuery>query).list = (<SearchListQuery>query).list.map(q => SearchQueryDTO.negate(q));
|
||||||
|
return query;
|
||||||
|
|
||||||
|
case SearchQueryTypes.orientation:
|
||||||
|
(<OrientationSearch>query).landscape = !(<OrientationSearch>query).landscape;
|
||||||
|
return query;
|
||||||
|
|
||||||
|
case SearchQueryTypes.date:
|
||||||
|
case SearchQueryTypes.rating:
|
||||||
|
case SearchQueryTypes.resolution:
|
||||||
|
case SearchQueryTypes.distance:
|
||||||
|
case SearchQueryTypes.any_text:
|
||||||
|
case SearchQueryTypes.person:
|
||||||
|
case SearchQueryTypes.position:
|
||||||
|
case SearchQueryTypes.keyword:
|
||||||
|
case SearchQueryTypes.caption:
|
||||||
|
case SearchQueryTypes.file_name:
|
||||||
|
case SearchQueryTypes.directory:
|
||||||
|
(<NegatableSearchQuery>query).negate = !(<NegatableSearchQuery>query).negate;
|
||||||
|
return query;
|
||||||
|
|
||||||
|
case SearchQueryTypes.SOME_OF:
|
||||||
|
throw new Error('Some of not supported');
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchQueryDTO {
|
export interface SearchQueryDTO {
|
||||||
type: SearchQueryTypes;
|
type: SearchQueryTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ANDSearchQuery extends SearchQueryDTO {
|
|
||||||
type: SearchQueryTypes.AND;
|
|
||||||
list: SearchQueryDTO[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ORSearchQuery extends SearchQueryDTO {
|
|
||||||
type: SearchQueryTypes.OR;
|
|
||||||
list: SearchQueryDTO[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SomeOfSearchQuery extends SearchQueryDTO, RangeSearchQuery {
|
|
||||||
type: SearchQueryTypes.SOME_OF;
|
|
||||||
list: NegatableSearchQuery[];
|
|
||||||
min?: number; // at least this amount of items
|
|
||||||
max?: number; // maximum this amount of items
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NegatableSearchQuery extends SearchQueryDTO {
|
export interface NegatableSearchQuery extends SearchQueryDTO {
|
||||||
negate?: boolean; // if true negates the expression
|
negate?: boolean; // if true negates the expression
|
||||||
@ -58,6 +75,26 @@ export interface RangeSearchQuery extends SearchQueryDTO {
|
|||||||
max?: number;
|
max?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SearchListQuery extends SearchQueryDTO {
|
||||||
|
list: SearchQueryDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ANDSearchQuery extends SearchQueryDTO, SearchListQuery {
|
||||||
|
type: SearchQueryTypes.AND;
|
||||||
|
list: SearchQueryDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ORSearchQuery extends SearchQueryDTO, SearchListQuery {
|
||||||
|
type: SearchQueryTypes.OR;
|
||||||
|
list: SearchQueryDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SomeOfSearchQuery extends SearchQueryDTO, SearchListQuery {
|
||||||
|
type: SearchQueryTypes.SOME_OF;
|
||||||
|
list: NegatableSearchQuery[];
|
||||||
|
min?: number; // at least this amount of items
|
||||||
|
}
|
||||||
|
|
||||||
export interface TextSearch extends NegatableSearchQuery {
|
export interface TextSearch extends NegatableSearchQuery {
|
||||||
type: SearchQueryTypes.any_text |
|
type: SearchQueryTypes.any_text |
|
||||||
@ -87,20 +124,20 @@ export interface DateSearch extends NegatableSearchQuery {
|
|||||||
before?: number;
|
before?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RatingSearch extends NegatableSearchQuery, RangeSearchQuery {
|
export interface RatingSearch extends RangeSearchQuery, NegatableSearchQuery {
|
||||||
type: SearchQueryTypes.rating;
|
type: SearchQueryTypes.rating;
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResolutionSearch extends NegatableSearchQuery, RangeSearchQuery {
|
export interface ResolutionSearch extends RangeSearchQuery, NegatableSearchQuery {
|
||||||
type: SearchQueryTypes.resolution;
|
type: SearchQueryTypes.resolution;
|
||||||
min?: number; // in megapixels
|
min?: number; // in megapixels
|
||||||
max?: number; // in megapixels
|
max?: number; // in megapixels
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrientationSearch extends NegatableSearchQuery {
|
export interface OrientationSearch {
|
||||||
type: SearchQueryTypes.orientation;
|
type: SearchQueryTypes.orientation;
|
||||||
orientation: OrientationSearchTypes;
|
landscape: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,12 +9,12 @@ import {
|
|||||||
DateSearch,
|
DateSearch,
|
||||||
DistanceSearch,
|
DistanceSearch,
|
||||||
OrientationSearch,
|
OrientationSearch,
|
||||||
OrientationSearchTypes,
|
|
||||||
ORSearchQuery,
|
ORSearchQuery,
|
||||||
RatingSearch,
|
RatingSearch,
|
||||||
ResolutionSearch,
|
ResolutionSearch,
|
||||||
SearchQueryDTO,
|
SearchQueryDTO,
|
||||||
SearchQueryTypes,
|
SearchQueryTypes,
|
||||||
|
SomeOfSearchQuery,
|
||||||
TextSearch,
|
TextSearch,
|
||||||
TextSearchQueryTypes
|
TextSearchQueryTypes
|
||||||
} from '../../../../../src/common/entities/SearchQueryDTO';
|
} from '../../../../../src/common/entities/SearchQueryDTO';
|
||||||
@ -98,10 +98,11 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
const connection = await SQLConnection.getConnection();
|
const connection = await SQLConnection.getConnection();
|
||||||
ObjectManagers.getInstance().IndexingManager.indexDirectory = () => Promise.resolve(null);
|
ObjectManagers.getInstance().IndexingManager.indexDirectory = () => Promise.resolve(null);
|
||||||
|
|
||||||
|
|
||||||
const im = new IndexingManagerTest();
|
const im = new IndexingManagerTest();
|
||||||
await im.saveToDB(directory);
|
await im.saveToDB(directory);
|
||||||
await im.saveToDB(subDir);
|
// await im.saveToDB(subDir);
|
||||||
await im.saveToDB(subDir2);
|
// await im.saveToDB(subDir2);
|
||||||
|
|
||||||
if (ObjectManagers.getInstance().IndexingManager &&
|
if (ObjectManagers.getInstance().IndexingManager &&
|
||||||
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress) {
|
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress) {
|
||||||
@ -389,7 +390,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
searchText: null,
|
searchText: null,
|
||||||
searchType: null,
|
searchType: null,
|
||||||
directories: [],
|
directories: [],
|
||||||
media: [p, p2],
|
media: [p, p2, p4],
|
||||||
metaFile: [],
|
metaFile: [],
|
||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
}));
|
}));
|
||||||
@ -467,7 +468,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
searchText: null,
|
searchText: null,
|
||||||
searchType: null,
|
searchType: null,
|
||||||
directories: [],
|
directories: [],
|
||||||
media: [p, p2],
|
media: [p, p2, p4],
|
||||||
metaFile: [],
|
metaFile: [],
|
||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
}));
|
}));
|
||||||
@ -487,7 +488,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
searchText: null,
|
searchText: null,
|
||||||
searchType: null,
|
searchType: null,
|
||||||
directories: [],
|
directories: [],
|
||||||
media: [p, p2],
|
media: [p, p2, p4],
|
||||||
metaFile: [],
|
metaFile: [],
|
||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
}));
|
}));
|
||||||
@ -515,6 +516,84 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should minimum of', async () => {
|
||||||
|
const sm = new SearchManager();
|
||||||
|
|
||||||
|
let query: SomeOfSearchQuery = <SomeOfSearchQuery>{
|
||||||
|
type: SearchQueryTypes.SOME_OF,
|
||||||
|
list: [<TextSearch>{text: 'jpg', type: SearchQueryTypes.file_name},
|
||||||
|
<TextSearch>{text: 'mp4', type: SearchQueryTypes.file_name}]
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(query))).to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [p, p2, p_faceLess, p4, v],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
query = <SomeOfSearchQuery>{
|
||||||
|
type: SearchQueryTypes.SOME_OF,
|
||||||
|
list: [<TextSearch>{text: 'R2', type: SearchQueryTypes.person},
|
||||||
|
<TextSearch>{text: 'Anakin', type: SearchQueryTypes.person},
|
||||||
|
<TextSearch>{text: 'Luke', type: SearchQueryTypes.person}]
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(query))).to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [p, p2, p4],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
query.min = 2;
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(query))).to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [p, p2, p4],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
query.min = 3;
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(query))).to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
query = <SomeOfSearchQuery>{
|
||||||
|
type: SearchQueryTypes.SOME_OF,
|
||||||
|
min: 3,
|
||||||
|
list: [<TextSearch>{text: 'sw', type: SearchQueryTypes.file_name},
|
||||||
|
<TextSearch>{text: 'R2', type: SearchQueryTypes.person},
|
||||||
|
<TextSearch>{text: 'Kamino', type: SearchQueryTypes.position},
|
||||||
|
<TextSearch>{text: 'Han', type: SearchQueryTypes.person}]
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(query))).to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [p2],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('should search text', async () => {
|
describe('should search text', async () => {
|
||||||
it('as any', async () => {
|
it('as any', async () => {
|
||||||
const sm = new SearchManager();
|
const sm = new SearchManager();
|
||||||
@ -529,6 +608,17 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(<TextSearch>{text: 'sw', negate: true, type: SearchQueryTypes.any_text})))
|
||||||
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
expect(Utils.clone(await sm.aSearch(<TextSearch>{text: 'Boba', type: SearchQueryTypes.any_text})))
|
expect(Utils.clone(await sm.aSearch(<TextSearch>{text: 'Boba', type: SearchQueryTypes.any_text})))
|
||||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
searchText: null,
|
searchText: null,
|
||||||
@ -539,6 +629,28 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(<TextSearch>{text: 'Boba', negate: true, type: SearchQueryTypes.any_text})))
|
||||||
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [p2, p_faceLess, p4],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
// all should have faces
|
||||||
|
const sRet = await sm.aSearch(<TextSearch>{text: 'Boba', negate: true, type: SearchQueryTypes.any_text});
|
||||||
|
for (let i = 0; i < sRet.media.length; ++i) {
|
||||||
|
if (sRet.media[i].id === p_faceLess.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
console.log(sRet.media[i]);
|
||||||
|
expect((<PhotoDTO>sRet.media[i]).metadata.faces).to.be.not.an('undefined');
|
||||||
|
expect((<PhotoDTO>sRet.media[i]).metadata.faces).to.be.lengthOf.above(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
expect(Utils.clone(await sm.aSearch(<TextSearch>{
|
expect(Utils.clone(await sm.aSearch(<TextSearch>{
|
||||||
text: 'Boba',
|
text: 'Boba',
|
||||||
type: SearchQueryTypes.any_text,
|
type: SearchQueryTypes.any_text,
|
||||||
@ -815,6 +927,21 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(<DateSearch>{
|
||||||
|
before: p.metadata.creationDate,
|
||||||
|
after: p.metadata.creationDate,
|
||||||
|
negate: true,
|
||||||
|
type: SearchQueryTypes.date
|
||||||
|
})))
|
||||||
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [p2, p_faceLess, p4, v],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
expect(Utils.clone(await sm.aSearch(<DateSearch>{
|
expect(Utils.clone(await sm.aSearch(<DateSearch>{
|
||||||
before: p.metadata.creationDate + 1000000000,
|
before: p.metadata.creationDate + 1000000000,
|
||||||
after: 0, type: SearchQueryTypes.date
|
after: 0, type: SearchQueryTypes.date
|
||||||
@ -854,6 +981,16 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(<RatingSearch>{min: 0, max: 5, negate: true, type: SearchQueryTypes.rating})))
|
||||||
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
expect(Utils.clone(await sm.aSearch(<RatingSearch>{min: 2, max: 2, type: SearchQueryTypes.rating})))
|
expect(Utils.clone(await sm.aSearch(<RatingSearch>{min: 2, max: 2, type: SearchQueryTypes.rating})))
|
||||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
searchText: null,
|
searchText: null,
|
||||||
@ -864,6 +1001,15 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(<RatingSearch>{min: 2, max: 2, negate: true, type: SearchQueryTypes.rating})))
|
||||||
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [p, p_faceLess],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -900,6 +1046,16 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(<ResolutionSearch>{min: 2, max: 3, negate: true, type: SearchQueryTypes.resolution})))
|
||||||
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [p, v, p4],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
expect(Utils.clone(await sm.aSearch(<ResolutionSearch>{min: 3, type: SearchQueryTypes.resolution})))
|
expect(Utils.clone(await sm.aSearch(<ResolutionSearch>{min: 3, type: SearchQueryTypes.resolution})))
|
||||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
searchText: null,
|
searchText: null,
|
||||||
@ -917,7 +1073,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
const sm = new SearchManager();
|
const sm = new SearchManager();
|
||||||
|
|
||||||
expect(Utils.clone(await sm.aSearch(<OrientationSearch>{
|
expect(Utils.clone(await sm.aSearch(<OrientationSearch>{
|
||||||
orientation: OrientationSearchTypes.portrait,
|
landscape: false,
|
||||||
type: SearchQueryTypes.orientation
|
type: SearchQueryTypes.orientation
|
||||||
})))
|
})))
|
||||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
@ -930,7 +1086,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
expect(Utils.clone(await sm.aSearch(<OrientationSearch>{
|
expect(Utils.clone(await sm.aSearch(<OrientationSearch>{
|
||||||
orientation: OrientationSearchTypes.landscape,
|
landscape: true,
|
||||||
type: SearchQueryTypes.orientation
|
type: SearchQueryTypes.orientation
|
||||||
})))
|
})))
|
||||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
@ -984,6 +1140,22 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
|||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
expect(Utils.clone(await sm.aSearch(<DistanceSearch>{
|
||||||
|
from: {GPSData: {latitude: 0, longitude: 0}},
|
||||||
|
distance: 112 * 10, // number of km per degree = ~111km
|
||||||
|
negate: true,
|
||||||
|
type: SearchQueryTypes.distance
|
||||||
|
})))
|
||||||
|
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||||
|
searchText: null,
|
||||||
|
searchType: null,
|
||||||
|
directories: [],
|
||||||
|
media: [p_faceLess, p4],
|
||||||
|
metaFile: [],
|
||||||
|
resultOverflow: false
|
||||||
|
}));
|
||||||
|
|
||||||
expect(Utils.clone(await sm.aSearch(<DistanceSearch>{
|
expect(Utils.clone(await sm.aSearch(<DistanceSearch>{
|
||||||
from: {GPSData: {latitude: 10, longitude: 10}},
|
from: {GPSData: {latitude: 10, longitude: 10}},
|
||||||
distance: 1,
|
distance: 1,
|
||||||
|
@ -148,6 +148,9 @@ export class TestHelper {
|
|||||||
}, <FaceRegion>{
|
}, <FaceRegion>{
|
||||||
box: {height: 10, width: 10, left: 105, top: 105},
|
box: {height: 10, width: 10, left: 105, top: 105},
|
||||||
name: 'Arvíztűrő Tükörfúrógép'
|
name: 'Arvíztűrő Tükörfúrógép'
|
||||||
|
}, <FaceRegion>{
|
||||||
|
box: {height: 10, width: 10, left: 201, top: 201},
|
||||||
|
name: 'R2-D2'
|
||||||
}] as any[];
|
}] as any[];
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@ -178,6 +181,9 @@ export class TestHelper {
|
|||||||
}, <FaceRegion>{
|
}, <FaceRegion>{
|
||||||
box: {height: 10, width: 10, left: 101, top: 101},
|
box: {height: 10, width: 10, left: 101, top: 101},
|
||||||
name: 'Obivan Kenobi'
|
name: 'Obivan Kenobi'
|
||||||
|
}, <FaceRegion>{
|
||||||
|
box: {height: 10, width: 10, left: 201, top: 201},
|
||||||
|
name: 'R2-D2'
|
||||||
}] as any[];
|
}] as any[];
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@ -224,6 +230,7 @@ export class TestHelper {
|
|||||||
p.metadata.creationDate = Date.now() - 4000;
|
p.metadata.creationDate = Date.now() - 4000;
|
||||||
p.metadata.size.height = 3000;
|
p.metadata.size.height = 3000;
|
||||||
p.metadata.size.width = 2000;
|
p.metadata.size.width = 2000;
|
||||||
|
|
||||||
p.metadata.faces = [<FaceRegion>{
|
p.metadata.faces = [<FaceRegion>{
|
||||||
box: {height: 10, width: 10, left: 10, top: 10},
|
box: {height: 10, width: 10, left: 10, top: 10},
|
||||||
name: 'Kylo Ren'
|
name: 'Kylo Ren'
|
||||||
@ -233,7 +240,11 @@ export class TestHelper {
|
|||||||
}, <FaceRegion>{
|
}, <FaceRegion>{
|
||||||
box: {height: 10, width: 10, left: 101, top: 101},
|
box: {height: 10, width: 10, left: 101, top: 101},
|
||||||
name: 'Obivan Kenobi'
|
name: 'Obivan Kenobi'
|
||||||
|
}, <FaceRegion>{
|
||||||
|
box: {height: 10, width: 10, left: 201, top: 201},
|
||||||
|
name: 'R2-D2'
|
||||||
}] as any[];
|
}] as any[];
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user