mirror of
https://github.com/bpatrik/pigallery2.git
synced 2024-12-10 11:10:35 +02:00
Implementing search query parsing and stringifying. #58
This commit is contained in:
parent
33b6fbf624
commit
47703b6b84
@ -11,18 +11,21 @@ import {Brackets, SelectQueryBuilder, WhereExpression} from 'typeorm';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {
|
||||
ANDSearchQuery,
|
||||
DateSearch,
|
||||
DistanceSearch,
|
||||
FromDateSearch,
|
||||
MaxRatingSearch,
|
||||
MaxResolutionSearch,
|
||||
MinRatingSearch,
|
||||
MinResolutionSearch,
|
||||
OrientationSearch,
|
||||
ORSearchQuery,
|
||||
RatingSearch,
|
||||
ResolutionSearch,
|
||||
SearchListQuery,
|
||||
SearchQueryDTO,
|
||||
SearchQueryTypes,
|
||||
SomeOfSearchQuery,
|
||||
TextSearch,
|
||||
TextSearchQueryMatchTypes
|
||||
TextSearchQueryMatchTypes,
|
||||
ToDateSearch
|
||||
} from '../../../../common/entities/SearchQueryDTO';
|
||||
import {GalleryManager} from './GalleryManager';
|
||||
import {ObjectManagers} from '../../ObjectManagers';
|
||||
@ -266,75 +269,100 @@ export class SearchManager implements ISearchManager {
|
||||
return q;
|
||||
});
|
||||
|
||||
case SearchQueryTypes.date:
|
||||
case SearchQueryTypes.from_date:
|
||||
return new Brackets(q => {
|
||||
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');
|
||||
if (typeof (<FromDateSearch>query).value === 'undefined') {
|
||||
throw new Error('Invalid search query: Date Query should contain from value');
|
||||
}
|
||||
const whereFN = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
|
||||
const relation = (<TextSearch>query).negate ? '<' : '>=';
|
||||
const relationRev = (<TextSearch>query).negate ? '>' : '<=';
|
||||
|
||||
if (typeof (<DateSearch>query).after !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['after' + paramCounter.value] = (<DateSearch>query).after;
|
||||
q.where(`media.metadata.creationDate ${relation} :after${paramCounter.value}`, textParam);
|
||||
}
|
||||
const textParam: any = {};
|
||||
textParam['from' + paramCounter.value] = (<FromDateSearch>query).value;
|
||||
q.where(`media.metadata.creationDate ${relation} :from${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++;
|
||||
return q;
|
||||
});
|
||||
|
||||
case SearchQueryTypes.rating:
|
||||
case SearchQueryTypes.to_date:
|
||||
return new Brackets(q => {
|
||||
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');
|
||||
if (typeof (<ToDateSearch>query).value === 'undefined') {
|
||||
throw new Error('Invalid search query: Date Query should contain to value');
|
||||
}
|
||||
const relation = (<TextSearch>query).negate ? '>' : '<=';
|
||||
|
||||
const textParam: any = {};
|
||||
textParam['to' + paramCounter.value] = (<ToDateSearch>query).value;
|
||||
q.where(`media.metadata.creationDate ${relation} :to${paramCounter.value}`, textParam);
|
||||
|
||||
paramCounter.value++;
|
||||
return q;
|
||||
});
|
||||
|
||||
case SearchQueryTypes.min_rating:
|
||||
return new Brackets(q => {
|
||||
if (typeof (<MinRatingSearch>query).value === 'undefined') {
|
||||
throw new Error('Invalid search query: Rating Query should contain minvalue');
|
||||
}
|
||||
|
||||
const whereFN = (<TextSearch>query).negate ? 'orWhere' : 'andWhere';
|
||||
const relation = (<TextSearch>query).negate ? '<' : '>=';
|
||||
const relationRev = (<TextSearch>query).negate ? '>' : '<=';
|
||||
if (typeof (<RatingSearch>query).min !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['min' + paramCounter.value] = (<RatingSearch>query).min;
|
||||
q.where(`media.metadata.rating ${relation} :min${paramCounter.value}`, textParam);
|
||||
|
||||
const textParam: any = {};
|
||||
textParam['min' + paramCounter.value] = (<MinRatingSearch>query).value;
|
||||
q.where(`media.metadata.rating ${relation} :min${paramCounter.value}`, textParam);
|
||||
|
||||
paramCounter.value++;
|
||||
return q;
|
||||
});
|
||||
case SearchQueryTypes.max_rating:
|
||||
return new Brackets(q => {
|
||||
if (typeof (<MaxRatingSearch>query).value === 'undefined') {
|
||||
throw new Error('Invalid search query: Rating Query should contain max value');
|
||||
}
|
||||
|
||||
if (typeof (<RatingSearch>query).max !== 'undefined') {
|
||||
const relation = (<TextSearch>query).negate ? '>' : '<=';
|
||||
|
||||
if (typeof (<MaxRatingSearch>query).value !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['max' + paramCounter.value] = (<RatingSearch>query).max;
|
||||
q[whereFN](`media.metadata.rating ${relationRev} :max${paramCounter.value}`, textParam);
|
||||
textParam['max' + paramCounter.value] = (<MaxRatingSearch>query).value;
|
||||
q.where(`media.metadata.rating ${relation} :max${paramCounter.value}`, textParam);
|
||||
}
|
||||
paramCounter.value++;
|
||||
return q;
|
||||
});
|
||||
|
||||
case SearchQueryTypes.resolution:
|
||||
case SearchQueryTypes.min_resolution:
|
||||
return new Brackets(q => {
|
||||
if (typeof (<ResolutionSearch>query).min === 'undefined' && typeof (<ResolutionSearch>query).max === 'undefined') {
|
||||
if (typeof (<MinResolutionSearch>query).value === 'undefined') {
|
||||
throw new Error('Invalid search query: Resolution Query should contain min value');
|
||||
}
|
||||
|
||||
const relation = (<TextSearch>query).negate ? '<' : '>=';
|
||||
|
||||
const textParam: any = {};
|
||||
textParam['min' + paramCounter.value] = (<MinResolutionSearch>query).value * 1000 * 1000;
|
||||
q.where(`media.metadata.size.width * media.metadata.size.height ${relation} :min${paramCounter.value}`, textParam);
|
||||
|
||||
|
||||
paramCounter.value++;
|
||||
return q;
|
||||
});
|
||||
|
||||
case SearchQueryTypes.max_resolution:
|
||||
return new Brackets(q => {
|
||||
if (typeof (<MaxResolutionSearch>query).value === 'undefined') {
|
||||
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') {
|
||||
const textParam: any = {};
|
||||
textParam['min' + paramCounter.value] = (<RatingSearch>query).min * 1000 * 1000;
|
||||
q.where(`media.metadata.size.width * media.metadata.size.height ${relation} :min${paramCounter.value}`, textParam);
|
||||
}
|
||||
const relation = (<TextSearch>query).negate ? '>' : '<=';
|
||||
|
||||
const textParam: any = {};
|
||||
textParam['max' + paramCounter.value] = (<MaxResolutionSearch>query).value * 1000 * 1000;
|
||||
q.where(`media.metadata.size.width * media.metadata.size.height ${relation} :max${paramCounter.value}`, textParam);
|
||||
|
||||
if (typeof (<ResolutionSearch>query).max !== 'undefined') {
|
||||
const textParam: any = {};
|
||||
textParam['max' + paramCounter.value] = (<RatingSearch>query).max * 1000 * 1000;
|
||||
q[whereFN](`media.metadata.size.width * media.metadata.size.height ${relationRev} :max${paramCounter.value}`, textParam);
|
||||
}
|
||||
paramCounter.value++;
|
||||
return q;
|
||||
});
|
||||
|
@ -54,6 +54,11 @@ export class Utils {
|
||||
return ret.substr(ret.length - length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the two input (let them be objects or arrays or just primitives) are equal
|
||||
* @param object
|
||||
* @param filter
|
||||
*/
|
||||
static equalsFilter(object: any, filter: any): boolean {
|
||||
if (typeof filter !== 'object' || filter == null) {
|
||||
return object === filter;
|
||||
|
@ -1,13 +1,19 @@
|
||||
import {GPSMetadata} from './PhotoDTO';
|
||||
import {Utils} from '../Utils';
|
||||
|
||||
export enum SearchQueryTypes {
|
||||
AND = 1, OR, SOME_OF,
|
||||
|
||||
// non-text metadata
|
||||
date = 10,
|
||||
rating,
|
||||
// |- range types
|
||||
from_date = 10,
|
||||
to_date,
|
||||
min_rating,
|
||||
max_rating,
|
||||
min_resolution,
|
||||
max_resolution,
|
||||
|
||||
distance,
|
||||
resolution,
|
||||
orientation,
|
||||
|
||||
// TEXT search types
|
||||
@ -34,24 +40,33 @@ export const TextSearchQueryTypes = [
|
||||
SearchQueryTypes.person,
|
||||
SearchQueryTypes.position,
|
||||
];
|
||||
export const MinRangeSearchQueryTypes = [
|
||||
SearchQueryTypes.from_date,
|
||||
SearchQueryTypes.min_rating,
|
||||
SearchQueryTypes.min_resolution,
|
||||
];
|
||||
export const MaxRangeSearchQueryTypes = [
|
||||
SearchQueryTypes.to_date,
|
||||
SearchQueryTypes.max_rating,
|
||||
SearchQueryTypes.max_resolution
|
||||
];
|
||||
|
||||
export const RangeSearchQueryTypes = MinRangeSearchQueryTypes.concat(MaxRangeSearchQueryTypes);
|
||||
|
||||
export const MetadataSearchQueryTypes = [
|
||||
// non-text metadata
|
||||
SearchQueryTypes.date,
|
||||
SearchQueryTypes.rating,
|
||||
SearchQueryTypes.distance,
|
||||
SearchQueryTypes.resolution,
|
||||
SearchQueryTypes.orientation,
|
||||
SearchQueryTypes.orientation
|
||||
].concat(RangeSearchQueryTypes)
|
||||
.concat(TextSearchQueryTypes);
|
||||
|
||||
// TEXT search types
|
||||
SearchQueryTypes.any_text,
|
||||
SearchQueryTypes.caption,
|
||||
SearchQueryTypes.directory,
|
||||
SearchQueryTypes.file_name,
|
||||
SearchQueryTypes.keyword,
|
||||
SearchQueryTypes.person,
|
||||
SearchQueryTypes.position,
|
||||
];
|
||||
export const rangedTypePairs: any = {};
|
||||
rangedTypePairs[SearchQueryTypes.from_date] = SearchQueryTypes.to_date;
|
||||
rangedTypePairs[SearchQueryTypes.min_rating] = SearchQueryTypes.max_rating;
|
||||
rangedTypePairs[SearchQueryTypes.min_resolution] = SearchQueryTypes.max_resolution;
|
||||
// add the other direction too
|
||||
for (const key of Object.keys(rangedTypePairs)) {
|
||||
rangedTypePairs[rangedTypePairs[key]] = key;
|
||||
}
|
||||
|
||||
export enum TextSearchQueryMatchTypes {
|
||||
exact_match = 1, like = 2
|
||||
@ -59,6 +74,12 @@ export enum TextSearchQueryMatchTypes {
|
||||
|
||||
|
||||
export namespace SearchQueryDTO {
|
||||
export const getRangedQueryPair = (type: SearchQueryTypes): SearchQueryTypes => {
|
||||
if (rangedTypePairs[type]) {
|
||||
return rangedTypePairs[type];
|
||||
}
|
||||
throw new Error('Unknown ranged type');
|
||||
};
|
||||
export const negate = (query: SearchQueryDTO): SearchQueryDTO => {
|
||||
switch (query.type) {
|
||||
case SearchQueryTypes.AND:
|
||||
@ -74,9 +95,12 @@ export namespace SearchQueryDTO {
|
||||
(<OrientationSearch>query).landscape = !(<OrientationSearch>query).landscape;
|
||||
return query;
|
||||
|
||||
case SearchQueryTypes.date:
|
||||
case SearchQueryTypes.rating:
|
||||
case SearchQueryTypes.resolution:
|
||||
case SearchQueryTypes.from_date:
|
||||
case SearchQueryTypes.to_date:
|
||||
case SearchQueryTypes.min_rating:
|
||||
case SearchQueryTypes.max_rating:
|
||||
case SearchQueryTypes.min_resolution:
|
||||
case SearchQueryTypes.max_resolution:
|
||||
case SearchQueryTypes.distance:
|
||||
case SearchQueryTypes.any_text:
|
||||
case SearchQueryTypes.person:
|
||||
@ -95,60 +119,211 @@ export namespace SearchQueryDTO {
|
||||
throw new Error('Unknown type' + query.type);
|
||||
}
|
||||
};
|
||||
|
||||
export const parse = (str: string): SearchQueryDTO => {
|
||||
console.log(str);
|
||||
str = str.replace(/\s\s+/g, ' ') // remove double spaces
|
||||
.replace(/:\s+/g, ':').replace(/\)(?=\S)/g, ') ').trim();
|
||||
|
||||
if (str.charAt(0) === '(' && str.charAt(str.length - 1) === ')') {
|
||||
str = str.slice(1, str.length - 1);
|
||||
}
|
||||
const fistNonBRSpace = () => {
|
||||
const bracketIn = [];
|
||||
for (let i = 0; i < str.length; ++i) {
|
||||
if (str.charAt(i) === '(') {
|
||||
bracketIn.push(i);
|
||||
continue;
|
||||
}
|
||||
if (str.charAt(i) === ')') {
|
||||
bracketIn.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bracketIn.length === 0 && str.charAt(i) === ' ') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return str.length - 1;
|
||||
};
|
||||
|
||||
// tokenize
|
||||
const tokenEnd = fistNonBRSpace();
|
||||
|
||||
if (tokenEnd !== str.length - 1) {
|
||||
if (str.startsWith(' and', tokenEnd)) {
|
||||
return <ANDSearchQuery>{
|
||||
type: SearchQueryTypes.AND,
|
||||
list: [SearchQueryDTO.parse(str.slice(0, tokenEnd)), // trim brackets
|
||||
SearchQueryDTO.parse(str.slice(tokenEnd + 4))]
|
||||
};
|
||||
} else {
|
||||
let padding = 0;
|
||||
if (str.startsWith(' or', tokenEnd)) {
|
||||
padding = 3;
|
||||
}
|
||||
return <ORSearchQuery>{
|
||||
type: SearchQueryTypes.OR,
|
||||
list: [SearchQueryDTO.parse(str.slice(0, tokenEnd)), // trim brackets
|
||||
SearchQueryDTO.parse(str.slice(tokenEnd + padding))]
|
||||
};
|
||||
}
|
||||
}
|
||||
if (str.startsWith('some-of:') ||
|
||||
new RegExp(/^\d*-of:/).test(str)) {
|
||||
const prefix = str.startsWith('some-of:') ? 'some-of:' : new RegExp(/^\d*-of:/).exec(str)[0];
|
||||
let tmpList: any = SearchQueryDTO.parse(str.slice(prefix.length + 1, -1)); // trim brackets
|
||||
const unfoldList = (q: SearchListQuery): SearchQueryDTO[] => {
|
||||
if (q.list) {
|
||||
return [].concat.apply([], q.list.map(e => unfoldList(<any>e))); // flatten array
|
||||
}
|
||||
return [q];
|
||||
};
|
||||
tmpList = unfoldList(<SearchListQuery>tmpList);
|
||||
const ret = <SomeOfSearchQuery>{
|
||||
type: SearchQueryTypes.SOME_OF,
|
||||
list: tmpList
|
||||
};
|
||||
if (new RegExp(/^\d*-of:/).test(str)) {
|
||||
ret.min = parseInt(new RegExp(/^\d*/).exec(str)[0], 10);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (str.startsWith('from:')) {
|
||||
return <FromDateSearch>{
|
||||
type: SearchQueryTypes.from_date,
|
||||
value: Date.parse(str.slice('from:'.length + 1, str.length - 1))
|
||||
};
|
||||
}
|
||||
if (str.startsWith('to:')) {
|
||||
return <ToDateSearch>{
|
||||
type: SearchQueryTypes.to_date,
|
||||
value: Date.parse(str.slice('to:'.length + 1, str.length - 1))
|
||||
};
|
||||
}
|
||||
|
||||
if (str.startsWith('min-rating:')) {
|
||||
return <MinRatingSearch>{
|
||||
type: SearchQueryTypes.min_rating,
|
||||
value: parseInt(str.slice('min-rating:'.length), 10)
|
||||
};
|
||||
}
|
||||
if (str.startsWith('max-rating:')) {
|
||||
return <MaxRatingSearch>{
|
||||
type: SearchQueryTypes.max_rating,
|
||||
value: parseInt(str.slice('max-rating:'.length), 10)
|
||||
};
|
||||
}
|
||||
if (str.startsWith('min-resolution:')) {
|
||||
return <MinResolutionSearch>{
|
||||
type: SearchQueryTypes.min_resolution,
|
||||
value: parseInt(str.slice('min-resolution:'.length), 10)
|
||||
};
|
||||
}
|
||||
if (str.startsWith('max-resolution:')) {
|
||||
return <MaxResolutionSearch>{
|
||||
type: SearchQueryTypes.max_resolution,
|
||||
value: parseInt(str.slice('max-resolution:'.length), 10)
|
||||
};
|
||||
}
|
||||
if (new RegExp(/^\d*-km-from:/).test(str)) {
|
||||
let from = str.slice(new RegExp(/^\d*-km-from:/).exec(str)[0].length);
|
||||
if (from.charAt(0) === '(' && from.charAt(from.length - 1) === ')') {
|
||||
from = from.slice(1, from.length - 1);
|
||||
}
|
||||
return <DistanceSearch>{
|
||||
type: SearchQueryTypes.distance,
|
||||
distance: parseInt(new RegExp(/^\d*/).exec(str)[0], 10),
|
||||
from: {text: from}
|
||||
};
|
||||
}
|
||||
|
||||
if (str.startsWith('orientation:')) {
|
||||
return <OrientationSearch>{
|
||||
type: SearchQueryTypes.orientation,
|
||||
landscape: str.slice('orientation:'.length) === 'landscape'
|
||||
};
|
||||
}
|
||||
|
||||
// parse text search
|
||||
const tmp = TextSearchQueryTypes.map(type => ({
|
||||
key: SearchQueryTypes[type] + ':',
|
||||
queryTemplate: <TextSearch>{type: type, text: ''}
|
||||
}));
|
||||
for (let i = 0; i < tmp.length; ++i) {
|
||||
if (str.startsWith(tmp[i].key)) {
|
||||
const ret: TextSearch = Utils.clone(tmp[i].queryTemplate);
|
||||
if (str.charAt(tmp[i].key.length) === '"' && str.charAt(str.length - 1) === '"') {
|
||||
ret.text = str.slice(tmp[i].key.length + 1, str.length - 1);
|
||||
ret.matchType = TextSearchQueryMatchTypes.exact_match;
|
||||
} else if (str.charAt(tmp[i].key.length) === '(' && str.charAt(str.length - 1) === ')') {
|
||||
ret.text = str.slice(tmp[i].key.length + 1, str.length - 1);
|
||||
} else {
|
||||
ret.text = str.slice(tmp[i].key.length);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return <TextSearch>{type: SearchQueryTypes.any_text, text: str};
|
||||
};
|
||||
|
||||
export const stringify = (query: SearchQueryDTO): string => {
|
||||
if (!query || !query.type) {
|
||||
return '';
|
||||
}
|
||||
switch (query.type) {
|
||||
case SearchQueryTypes.AND:
|
||||
return '(' + (<SearchListQuery>query).list.map(q => SearchQueryDTO.stringify(q)).join(' AND ') + ')';
|
||||
return '(' + (<SearchListQuery>query).list.map(q => SearchQueryDTO.stringify(q)).join(' and ') + ')';
|
||||
|
||||
case SearchQueryTypes.OR:
|
||||
return '(' + (<SearchListQuery>query).list.map(q => SearchQueryDTO.stringify(q)).join(' OR ') + ')';
|
||||
return '(' + (<SearchListQuery>query).list.map(q => SearchQueryDTO.stringify(q)).join(' or ') + ')';
|
||||
|
||||
case SearchQueryTypes.SOME_OF:
|
||||
if ((<SomeOfSearchQuery>query).min) {
|
||||
return (<SomeOfSearchQuery>query).min + ' OF: (' +
|
||||
(<SearchListQuery>query).list.map(q => SearchQueryDTO.stringify(q)).join(', ') + ')';
|
||||
return (<SomeOfSearchQuery>query).min + '-of:(' +
|
||||
(<SearchListQuery>query).list.map(q => SearchQueryDTO.stringify(q)).join(' ') + ')';
|
||||
}
|
||||
return 'SOME OF: (' +
|
||||
(<SearchListQuery>query).list.map(q => SearchQueryDTO.stringify(q)).join(', ') + ')';
|
||||
return 'some-of:(' +
|
||||
(<SearchListQuery>query).list.map(q => SearchQueryDTO.stringify(q)).join(' ') + ')';
|
||||
|
||||
|
||||
case SearchQueryTypes.orientation:
|
||||
return 'orientation: ' + (<OrientationSearch>query).landscape ? 'landscape' : 'portrait';
|
||||
return 'orientation:' + ((<OrientationSearch>query).landscape ? 'landscape' : 'portrait');
|
||||
|
||||
case SearchQueryTypes.date:
|
||||
let ret = '';
|
||||
if ((<DateSearch>query).after) {
|
||||
ret += 'from: ' + (<DateSearch>query).after;
|
||||
case SearchQueryTypes.from_date:
|
||||
if (!(<FromDateSearch>query).value) {
|
||||
return '';
|
||||
}
|
||||
if ((<DateSearch>query).before) {
|
||||
ret += ' to: ' + (<DateSearch>query).before;
|
||||
return 'from:(' + new Date((<FromDateSearch>query).value).toLocaleDateString() + ')'.trim();
|
||||
case SearchQueryTypes.to_date:
|
||||
if (!(<ToDateSearch>query).value) {
|
||||
return '';
|
||||
}
|
||||
return ret.trim();
|
||||
case SearchQueryTypes.rating:
|
||||
let rating = '';
|
||||
if ((<RatingSearch>query).min) {
|
||||
rating += 'min-rating: ' + (<RatingSearch>query).min;
|
||||
}
|
||||
if ((<RatingSearch>query).max) {
|
||||
rating += ' max-rating: ' + (<RatingSearch>query).max;
|
||||
}
|
||||
return rating.trim();
|
||||
case SearchQueryTypes.resolution:
|
||||
let res = '';
|
||||
if ((<ResolutionSearch>query).min) {
|
||||
res += 'min-resolution: ' + (<ResolutionSearch>query).min;
|
||||
}
|
||||
if ((<RatingSearch>query).max) {
|
||||
res += ' max-resolution: ' + (<ResolutionSearch>query).max;
|
||||
}
|
||||
return res.trim();
|
||||
return 'to:(' + new Date((<ToDateSearch>query).value).toLocaleDateString() + ')'.trim();
|
||||
case SearchQueryTypes.min_rating:
|
||||
return 'min-rating:' + (isNaN((<RangeSearch>query).value) ? '' : (<RangeSearch>query).value);
|
||||
case SearchQueryTypes.max_rating:
|
||||
return 'max-rating:' + (isNaN((<RangeSearch>query).value) ? '' : (<RangeSearch>query).value);
|
||||
case SearchQueryTypes.min_resolution:
|
||||
return 'min-resolution:' + (isNaN((<RangeSearch>query).value) ? '' : (<RangeSearch>query).value);
|
||||
case SearchQueryTypes.max_resolution:
|
||||
return 'max-resolution:' + (isNaN((<RangeSearch>query).value) ? '' : (<RangeSearch>query).value);
|
||||
case SearchQueryTypes.distance:
|
||||
return (<DistanceSearch>query).distance + ' km from: ' + (<DistanceSearch>query).from.text;
|
||||
if ((<DistanceSearch>query).from.text.indexOf(' ') !== -1) {
|
||||
return (<DistanceSearch>query).distance + '-km-from:(' + (<DistanceSearch>query).from.text + ')';
|
||||
}
|
||||
return (<DistanceSearch>query).distance + '-km-from:' + (<DistanceSearch>query).from.text;
|
||||
|
||||
case SearchQueryTypes.any_text:
|
||||
if ((<TextSearch>query).matchType === TextSearchQueryMatchTypes.exact_match) {
|
||||
return '"' + (<TextSearch>query).text + '"';
|
||||
|
||||
} else if ((<TextSearch>query).text.indexOf(' ') !== -1) {
|
||||
return '(' + (<TextSearch>query).text + ')';
|
||||
}
|
||||
return (<TextSearch>query).text;
|
||||
|
||||
case SearchQueryTypes.person:
|
||||
@ -160,6 +335,12 @@ export namespace SearchQueryDTO {
|
||||
if (!(<TextSearch>query).text) {
|
||||
return '';
|
||||
}
|
||||
if ((<TextSearch>query).matchType === TextSearchQueryMatchTypes.exact_match) {
|
||||
return SearchQueryTypes[query.type] + ':"' + (<TextSearch>query).text + '"';
|
||||
|
||||
} else if ((<TextSearch>query).text.indexOf(' ') !== -1) {
|
||||
return SearchQueryTypes[query.type] + ':(' + (<TextSearch>query).text + ')';
|
||||
}
|
||||
return SearchQueryTypes[query.type] + ':' + (<TextSearch>query).text;
|
||||
|
||||
default:
|
||||
@ -177,11 +358,6 @@ export interface NegatableSearchQuery extends SearchQueryDTO {
|
||||
negate?: boolean; // if true negates the expression
|
||||
}
|
||||
|
||||
export interface RangeSearchQuery extends SearchQueryDTO {
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
export interface SearchListQuery extends SearchQueryDTO {
|
||||
list: SearchQueryDTO[];
|
||||
}
|
||||
@ -225,22 +401,42 @@ export interface DistanceSearch extends NegatableSearchQuery {
|
||||
}
|
||||
|
||||
|
||||
export interface DateSearch extends NegatableSearchQuery {
|
||||
type: SearchQueryTypes.date;
|
||||
after?: number;
|
||||
before?: number;
|
||||
export interface RangeSearch extends NegatableSearchQuery {
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface RatingSearch extends RangeSearchQuery, NegatableSearchQuery {
|
||||
type: SearchQueryTypes.rating;
|
||||
min?: number;
|
||||
max?: number;
|
||||
export interface RangeSearchGroup extends ANDSearchQuery {
|
||||
list: RangeSearch[];
|
||||
}
|
||||
|
||||
export interface ResolutionSearch extends RangeSearchQuery, NegatableSearchQuery {
|
||||
type: SearchQueryTypes.resolution;
|
||||
min?: number; // in megapixels
|
||||
max?: number; // in megapixels
|
||||
export interface FromDateSearch extends RangeSearch {
|
||||
type: SearchQueryTypes.from_date;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface ToDateSearch extends RangeSearch {
|
||||
type: SearchQueryTypes.to_date;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface MinRatingSearch extends RangeSearch {
|
||||
type: SearchQueryTypes.min_rating;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface MaxRatingSearch extends RangeSearch {
|
||||
type: SearchQueryTypes.max_rating;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface MinResolutionSearch extends RangeSearch {
|
||||
type: SearchQueryTypes.min_resolution;
|
||||
value: number; // in megapixels
|
||||
}
|
||||
|
||||
export interface MaxResolutionSearch extends RangeSearch {
|
||||
type: SearchQueryTypes.max_resolution;
|
||||
value: number; // in megapixels
|
||||
}
|
||||
|
||||
export interface OrientationSearch {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="row mt-1 mb-1" *ngIf="queryEntry">
|
||||
<ng-container *ngIf="IsListQuery">
|
||||
<div class="input-group col-md-2">
|
||||
<div class="input-group col-md-3">
|
||||
<select
|
||||
id="listSearchType"
|
||||
name="listSearchType"
|
||||
@ -24,20 +24,22 @@
|
||||
name="someOfMinValue"
|
||||
id="someOfMinValue"
|
||||
required="required">
|
||||
<div class="col-md-3"></div>
|
||||
<div class="col-md-2"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="queryEntry.type != SearchQueryTypes.SOME_OF">
|
||||
<div class="col-md-9"></div>
|
||||
<div class="col-md-8"></div>
|
||||
</ng-container>
|
||||
<button [ngClass]="true? 'btn-danger':'btn-secondary'"
|
||||
class="btn float-right col-md-1">
|
||||
|
||||
<button [ngClass]="'btn-danger'"
|
||||
class="btn float-right col-md-1"
|
||||
(click)="deleteItem()">
|
||||
<span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span>
|
||||
</button>
|
||||
<div class="container query-list">
|
||||
<app-gallery-search-query-entry *ngFor="let sq of AsListQuery.list; index as i"
|
||||
[(ngModel)]="sq"
|
||||
(delete)="itemDeleted(i)">
|
||||
(delete)="itemDeleted(i)">
|
||||
</app-gallery-search-query-entry>
|
||||
</div>
|
||||
<button class="btn btn-primary mx-auto" (click)="addQuery()">
|
||||
@ -45,7 +47,7 @@
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!IsListQuery">
|
||||
<div class="input-group col-md-2">
|
||||
<div class="input-group col-md-3">
|
||||
<select
|
||||
id="searchType"
|
||||
name="searchType"
|
||||
@ -56,18 +58,19 @@
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group col-md-9" *ngIf="IsTextQuery">
|
||||
<div class="input-group col-md-8" *ngIf="IsTextQuery">
|
||||
<input
|
||||
id="searchField"
|
||||
name="searchField"
|
||||
placeholder="link"
|
||||
placeholder="Search text"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsTextQuery.text"
|
||||
(change)="onChange(queryEntry)"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
type="text"/>
|
||||
</div>
|
||||
<ng-container [ngSwitch]="queryEntry.type">
|
||||
<div *ngSwitchCase="SearchQueryTypes.distance" class="col-md-9 d-flex">
|
||||
<div *ngSwitchCase="SearchQueryTypes.distance" class="col-md-8 d-flex">
|
||||
<div class="input-group col-md-4">
|
||||
<input type="number" class="form-control" placeholder="1"
|
||||
id="distance"
|
||||
@ -93,98 +96,94 @@
|
||||
type="text">
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="SearchQueryTypes.date" class="col-md-9 d-flex">
|
||||
<div class="input-group col-md-6">
|
||||
<label class="col-md-4 control-label" for="maxResolution">From:</label>
|
||||
<input id="afterDate"
|
||||
name="afterDate"
|
||||
title="After"
|
||||
i18n-title
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsDateQuery.after"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
type="date">
|
||||
</div>
|
||||
<div class="input-group col-md-6">
|
||||
<label class="col-md-4 control-label" for="maxResolution">To:</label>
|
||||
<input id="beforeDate"
|
||||
name="beforeDate"
|
||||
title="Before"
|
||||
i18n-title
|
||||
[(ngModel)]="AsDateQuery.before"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
class="form-control input-md"
|
||||
type="date">
|
||||
<!-- Range Search Query -->
|
||||
<div *ngSwitchCase="SearchQueryTypes.from_date" class="col-md-8 input-group ">
|
||||
<label class="col-md-4 control-label" for="from_date">From:</label>
|
||||
<input id="from_date"
|
||||
name="from_date"
|
||||
title="From date"
|
||||
i18n-title
|
||||
[ngModel]="AsRangeQuery.value | date:'yyyy-MM-dd'"
|
||||
(ngModelChange)="AsRangeQuery.value = $event; onChange(queryEntry) "
|
||||
[value]="AsRangeQuery.value | date:'yyyy-MM-dd'" #from_date="ngModel"
|
||||
class="form-control input-md"
|
||||
type="date">
|
||||
</div>
|
||||
<div *ngSwitchCase="SearchQueryTypes.to_date" class="col-md-8 input-group">
|
||||
<label class="col-md-4 control-label" for="to_date">To:</label>
|
||||
<input id="to_date"
|
||||
name="to_date"
|
||||
title="To date"
|
||||
i18n-title
|
||||
[ngModel]="AsRangeQuery.value | date:'yyyy-MM-dd'"
|
||||
(ngModelChange)="AsRangeQuery.value = $event; onChange(queryEntry) "
|
||||
[value]="AsRangeQuery.value | date:'yyyy-MM-dd'" #to_date="ngModel"
|
||||
class="form-control input-md"
|
||||
type="date">
|
||||
</div>
|
||||
<div *ngSwitchCase="SearchQueryTypes.min_rating" class="col-md-8 input-group">
|
||||
<label class="col-md-4 control-label" for="maxResolution">Min:</label>
|
||||
<input id="minRating"
|
||||
name="minRating"
|
||||
title="Minimum Rating"
|
||||
placeholder="0"
|
||||
i18n-title
|
||||
min="0"
|
||||
max="5"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsRangeQuery.value"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
type="number">
|
||||
</div>
|
||||
<div *ngSwitchCase="SearchQueryTypes.max_rating" class="col-md-8 input-group">
|
||||
<label class="col-md-4 control-label" for="maxResolution">Max:</label>
|
||||
<input id="maxRating"
|
||||
name="maxRating"
|
||||
title="Maximum Rating"
|
||||
placeholder="5"
|
||||
i18n-title
|
||||
min="0"
|
||||
max="5"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsRangeQuery.value"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
type="number">
|
||||
</div>
|
||||
<div *ngSwitchCase="SearchQueryTypes.min_resolution" class="col-md-8 input-group">
|
||||
|
||||
<label class="col-md-4 control-label" for="maxResolution">Min:</label>
|
||||
<input id="minResolution"
|
||||
name="minResolution"
|
||||
title="Minimum Rating"
|
||||
placeholder="0"
|
||||
i18n-title
|
||||
min="0"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsRangeQuery.value"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
type="number">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">Mpx</span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="SearchQueryTypes.rating" class="col-md-9 d-flex">
|
||||
<div class="input-group col-md-6">
|
||||
<label class="col-md-4 control-label" for="maxResolution">Min:</label>
|
||||
<input id="minRating"
|
||||
name="minRating"
|
||||
title="Minimum Rating"
|
||||
placeholder="0"
|
||||
i18n-title
|
||||
min="0"
|
||||
[max]="AsRatingQuery.max || 5"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsRatingQuery.min"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
type="number">
|
||||
</div>
|
||||
|
||||
<div class="input-group col-md-6">
|
||||
<label class="col-md-4 control-label" for="maxResolution">Max:</label>
|
||||
<input id="maxRating"
|
||||
name="maxRating"
|
||||
title="Maximum Rating"
|
||||
placeholder="5"
|
||||
i18n-title
|
||||
[min]="AsRatingQuery.min || 0"
|
||||
max="5"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsRatingQuery.max"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
type="number">
|
||||
<div *ngSwitchCase="SearchQueryTypes.max_resolution" class="col-md-8 input-group">
|
||||
<label class="col-md-4 control-label" for="maxResolution">Max:</label>
|
||||
<input id="maxResolution"
|
||||
name="maxResolution"
|
||||
title="Maximum Rating"
|
||||
placeholder="5"
|
||||
i18n-title
|
||||
[min]="0"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsRangeQuery.value"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
type="number">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">Mpx</span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="SearchQueryTypes.resolution" class="col-md-9 d-flex">
|
||||
|
||||
<div class="input-group col-md-6">
|
||||
<label class="col-md-4 control-label" for="maxResolution">Min:</label>
|
||||
<input id="minResolution"
|
||||
name="minResolution"
|
||||
title="Minimum Rating"
|
||||
placeholder="0"
|
||||
i18n-title
|
||||
min="0"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsResolutionQuery.min"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
type="number">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">Mpx</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group col-md-6">
|
||||
<label class="col-md-4 control-label" for="maxResolution">Max:</label>
|
||||
<input id="maxResolution"
|
||||
name="maxResolution"
|
||||
title="Maximum Rating"
|
||||
placeholder="5"
|
||||
i18n-title
|
||||
[min]="AsResolutionQuery.min || 0"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsResolutionQuery.max"
|
||||
(ngModelChange)="onChange(queryEntry)"
|
||||
type="number">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">Mpx</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="SearchQueryTypes.orientation" class="col-md-9 d-flex">
|
||||
<div *ngSwitchCase="SearchQueryTypes.orientation" class="col-md-8 d-flex">
|
||||
<div class="input-group col-md-6">
|
||||
<bSwitch
|
||||
class="switch"
|
||||
@ -206,11 +205,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<button [ngClass]="true? 'btn-danger':'btn-secondary'"
|
||||
<button [ngClass]="'btn-danger'"
|
||||
class="btn float-right col-md-1"
|
||||
(click)="deleteItem()">
|
||||
<span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
|
@ -1,11 +1,10 @@
|
||||
import {Component, EventEmitter, forwardRef, OnChanges, Output} from '@angular/core';
|
||||
import {
|
||||
DateSearch,
|
||||
DistanceSearch,
|
||||
ListSearchQueryTypes,
|
||||
OrientationSearch,
|
||||
RatingSearch,
|
||||
ResolutionSearch,
|
||||
RangeSearch,
|
||||
RangeSearchQueryTypes,
|
||||
SearchListQuery,
|
||||
SearchQueryDTO,
|
||||
SearchQueryTypes,
|
||||
@ -42,6 +41,11 @@ export class GallerySearchQueryEntryComponent implements ControlValueAccessor, V
|
||||
|
||||
constructor() {
|
||||
this.SearchQueryTypesEnum = Utils.enumToArray(SearchQueryTypes);
|
||||
// Range queries need to be added as AND with min and max sub entry
|
||||
this.SearchQueryTypesEnum.filter(e => !RangeSearchQueryTypes.includes(e.key));
|
||||
this.SearchQueryTypesEnum.push({value: 'Date', key: SearchQueryTypes.AND});
|
||||
this.SearchQueryTypesEnum.push({value: 'Rating', key: SearchQueryTypes.AND});
|
||||
this.SearchQueryTypesEnum.push({value: 'Resolution', key: SearchQueryTypes.AND});
|
||||
|
||||
}
|
||||
|
||||
@ -49,6 +53,7 @@ export class GallerySearchQueryEntryComponent implements ControlValueAccessor, V
|
||||
return this.queryEntry && TextSearchQueryTypes.includes(this.queryEntry.type);
|
||||
}
|
||||
|
||||
|
||||
get IsListQuery(): boolean {
|
||||
return this.queryEntry && ListSearchQueryTypes.includes(this.queryEntry.type);
|
||||
}
|
||||
@ -57,13 +62,10 @@ export class GallerySearchQueryEntryComponent implements ControlValueAccessor, V
|
||||
return <any>this.queryEntry;
|
||||
}
|
||||
|
||||
get AsDateQuery(): DateSearch {
|
||||
get AsRangeQuery(): RangeSearch {
|
||||
return <any>this.queryEntry;
|
||||
}
|
||||
|
||||
get AsResolutionQuery(): ResolutionSearch {
|
||||
return <any>this.queryEntry;
|
||||
}
|
||||
|
||||
get AsOrientationQuery(): OrientationSearch {
|
||||
return <any>this.queryEntry;
|
||||
@ -73,9 +75,6 @@ export class GallerySearchQueryEntryComponent implements ControlValueAccessor, V
|
||||
return <any>this.queryEntry;
|
||||
}
|
||||
|
||||
get AsRatingQuery(): RatingSearch {
|
||||
return <any>this.queryEntry;
|
||||
}
|
||||
|
||||
get AsSomeOfQuery(): SomeOfSearchQuery {
|
||||
return <any>this.queryEntry;
|
||||
@ -132,30 +131,35 @@ export class GallerySearchQueryEntryComponent implements ControlValueAccessor, V
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
// console.log('ngOnChanges', this.queryEntry);
|
||||
// console.log('ngOnChanges', this.queryEntry);
|
||||
|
||||
}
|
||||
|
||||
public onChange(value: any): void {
|
||||
// console.log('onChange', this.queryEntry);
|
||||
}
|
||||
|
||||
public onTouched(): void {
|
||||
}
|
||||
|
||||
public writeValue(obj: any): void {
|
||||
this.queryEntry = obj;
|
||||
// console.log('write value', this.queryEntry);
|
||||
// console.log('write value', this.queryEntry);
|
||||
this.ngOnChanges();
|
||||
}
|
||||
|
||||
public registerOnChange(fn: any): void {
|
||||
// console.log('registerOnChange', fn);
|
||||
this.onChange = fn;
|
||||
registerOnChange(fn: (_: any) => void): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
public registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
registerOnTouched(fn: () => void): void {
|
||||
this.propagateTouch = fn;
|
||||
}
|
||||
|
||||
public onChange(event: any) {
|
||||
this.propagateChange(this.queryEntry);
|
||||
}
|
||||
|
||||
private propagateChange = (_: any) => {
|
||||
};
|
||||
|
||||
private propagateTouch = (_: any) => {
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
(blur)="onFocusLost()"
|
||||
(focus)="onFocus()"
|
||||
[(ngModel)]="rawSearchText"
|
||||
(ngModelChange)="validateRawSearchText()"
|
||||
#name="ngModel"
|
||||
size="30"
|
||||
ngControl="search"
|
||||
@ -63,8 +64,8 @@
|
||||
class="form-control"
|
||||
i18n-placeholder
|
||||
placeholder="Search"
|
||||
disabled
|
||||
[ngModel]="rawSearchText"
|
||||
[(ngModel)]="rawSearchText"
|
||||
(ngModelChange)="validateRawSearchText()"
|
||||
size="30"
|
||||
name="srch-term-preview"
|
||||
id="srch-term-preview"
|
||||
@ -72,6 +73,7 @@
|
||||
|
||||
<app-gallery-search-query-entry
|
||||
[(ngModel)]="searchQueryDTO"
|
||||
(change)="onQueryChange()"
|
||||
name="search-root"
|
||||
(delete)="resetQuery()">
|
||||
|
||||
|
@ -21,6 +21,7 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
|
||||
autoCompleteItems: AutoCompleteRenderItem[] = [];
|
||||
public searchQueryDTO: SearchQueryDTO = <TextSearch>{type: SearchQueryTypes.any_text, text: ''};
|
||||
public rawSearchText: string;
|
||||
mouseOverAutoComplete = false;
|
||||
readonly SearchQueryTypes: typeof SearchQueryTypes;
|
||||
modalRef: BsModalRef;
|
||||
@ -48,12 +49,6 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
public get rawSearchText(): string {
|
||||
return SearchQueryDTO.stringify(this.searchQueryDTO);
|
||||
}
|
||||
|
||||
public set rawSearchText(val: string) {
|
||||
}
|
||||
|
||||
get HTMLSearchQuery() {
|
||||
const searchQuery: any = {};
|
||||
@ -81,7 +76,6 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public setMouseOverAutoComplete(value: boolean) {
|
||||
this.mouseOverAutoComplete = value;
|
||||
}
|
||||
@ -111,6 +105,18 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
this.searchQueryDTO = <TextSearch>{text: '', type: SearchQueryTypes.any_text};
|
||||
}
|
||||
|
||||
onQueryChange() {
|
||||
this.rawSearchText = SearchQueryDTO.stringify(this.searchQueryDTO);
|
||||
}
|
||||
|
||||
validateRawSearchText() {
|
||||
try {
|
||||
this.searchQueryDTO = SearchQueryDTO.parse(this.rawSearchText);
|
||||
console.log(this.searchQueryDTO);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private emptyAutoComplete() {
|
||||
this.autoCompleteItems = [];
|
||||
|
@ -5,17 +5,20 @@ import {Utils} from '../../../../../src/common/Utils';
|
||||
import {SQLTestHelper} from '../../../SQLTestHelper';
|
||||
import {
|
||||
ANDSearchQuery,
|
||||
DateSearch,
|
||||
DistanceSearch,
|
||||
FromDateSearch,
|
||||
MaxRatingSearch,
|
||||
MaxResolutionSearch,
|
||||
MinRatingSearch,
|
||||
MinResolutionSearch,
|
||||
OrientationSearch,
|
||||
ORSearchQuery,
|
||||
RatingSearch,
|
||||
ResolutionSearch,
|
||||
SearchQueryDTO,
|
||||
SearchQueryTypes,
|
||||
SomeOfSearchQuery,
|
||||
TextSearch,
|
||||
TextSearchQueryMatchTypes
|
||||
TextSearchQueryMatchTypes,
|
||||
ToDateSearch
|
||||
} from '../../../../../src/common/entities/SearchQueryDTO';
|
||||
import {IndexingManager} from '../../../../../src/backend/model/database/sql/IndexingManager';
|
||||
import {DirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
|
||||
@ -789,7 +792,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
it('should search date', async () => {
|
||||
const sm = new SearchManager();
|
||||
|
||||
let query = <DateSearch>{before: 0, after: 0, type: SearchQueryTypes.date};
|
||||
let query: any = <FromDateSearch>{value: 0, type: SearchQueryTypes.from_date};
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
@ -800,9 +803,8 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <DateSearch>{
|
||||
before: p.metadata.creationDate,
|
||||
after: p.metadata.creationDate, type: SearchQueryTypes.date
|
||||
query = <ToDateSearch>{
|
||||
value: p.metadata.creationDate, type: SearchQueryTypes.to_date
|
||||
};
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
@ -814,11 +816,10 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <DateSearch>{
|
||||
before: p.metadata.creationDate,
|
||||
after: p.metadata.creationDate,
|
||||
query = <FromDateSearch>{
|
||||
value: p.metadata.creationDate,
|
||||
negate: true,
|
||||
type: SearchQueryTypes.date
|
||||
type: SearchQueryTypes.from_date
|
||||
};
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
@ -830,15 +831,12 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <DateSearch>{
|
||||
before: p.metadata.creationDate + 1000000000,
|
||||
after: 0, type: SearchQueryTypes.date
|
||||
query = <ToDateSearch>{
|
||||
value: p.metadata.creationDate + 1000000000,
|
||||
type: SearchQueryTypes.to_date
|
||||
};
|
||||
|
||||
expect(Utils.clone(await sm.search(<DateSearch>{
|
||||
before: p.metadata.creationDate + 1000000000,
|
||||
after: 0, type: SearchQueryTypes.date
|
||||
})))
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchQuery: query,
|
||||
directories: [],
|
||||
@ -853,7 +851,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
it('should search rating', async () => {
|
||||
const sm = new SearchManager();
|
||||
|
||||
let query = <RatingSearch>{min: 0, max: 0, type: SearchQueryTypes.rating};
|
||||
let query: MinRatingSearch | MaxRatingSearch = <MinRatingSearch>{value: 0, type: SearchQueryTypes.min_rating};
|
||||
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
@ -865,7 +863,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <RatingSearch>{min: 0, max: 5, type: SearchQueryTypes.rating};
|
||||
query = <MaxRatingSearch>{value: 5, type: SearchQueryTypes.max_rating};
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchQuery: query,
|
||||
@ -875,7 +873,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <RatingSearch>{min: 0, max: 5, negate: true, type: SearchQueryTypes.rating};
|
||||
query = <MaxRatingSearch>{value: 5, negate: true, type: SearchQueryTypes.max_rating};
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchQuery: query,
|
||||
@ -885,7 +883,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <RatingSearch>{min: 2, max: 2, type: SearchQueryTypes.rating};
|
||||
query = <MinRatingSearch>{value: 2, type: SearchQueryTypes.min_rating};
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchQuery: query,
|
||||
@ -895,7 +893,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <RatingSearch>{min: 2, max: 2, negate: true, type: SearchQueryTypes.rating};
|
||||
query = <MinRatingSearch>{value: 2, negate: true, type: SearchQueryTypes.min_rating};
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchQuery: query,
|
||||
@ -910,7 +908,9 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
it('should search resolution', async () => {
|
||||
const sm = new SearchManager();
|
||||
|
||||
let query = <ResolutionSearch>{min: 0, max: 0, type: SearchQueryTypes.resolution};
|
||||
let query: MinResolutionSearch | MaxResolutionSearch =
|
||||
<MinResolutionSearch>{value: 0, type: SearchQueryTypes.min_resolution};
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchQuery: query,
|
||||
@ -920,7 +920,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <ResolutionSearch>{max: 1, type: SearchQueryTypes.resolution};
|
||||
query = <MaxResolutionSearch>{value: 1, type: SearchQueryTypes.max_resolution};
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchQuery: query,
|
||||
@ -930,7 +930,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <ResolutionSearch>{min: 2, max: 3, type: SearchQueryTypes.resolution};
|
||||
query = <MinResolutionSearch>{value: 2, type: SearchQueryTypes.min_resolution};
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchQuery: query,
|
||||
@ -940,7 +940,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <ResolutionSearch>{min: 2, max: 3, negate: true, type: SearchQueryTypes.resolution};
|
||||
query = <MaxResolutionSearch>{value: 3, negate: true, type: SearchQueryTypes.max_resolution};
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchQuery: query,
|
||||
@ -950,7 +950,7 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
resultOverflow: false
|
||||
}));
|
||||
|
||||
query = <ResolutionSearch>{min: 3, type: SearchQueryTypes.resolution};
|
||||
query = <MinResolutionSearch>{value: 3, negate: true, type: SearchQueryTypes.min_resolution};
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir(<SearchResultDTO>{
|
||||
searchQuery: query,
|
||||
@ -1094,6 +1094,8 @@ describe('SearchManager', (sqlHelper: SQLTestHelper) => {
|
||||
text: 'xyz',
|
||||
type: SearchQueryTypes.keyword
|
||||
};
|
||||
|
||||
// tslint:disable-next-line
|
||||
expect(await sm.getRandomPhoto(query)).to.not.exist;
|
||||
|
||||
query = <TextSearch>{
|
||||
|
137
test/common/unit/SearchQueryDTO.ts
Normal file
137
test/common/unit/SearchQueryDTO.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import {expect} from 'chai';
|
||||
import {
|
||||
ANDSearchQuery,
|
||||
DistanceSearch,
|
||||
FromDateSearch,
|
||||
MaxRatingSearch,
|
||||
MaxResolutionSearch,
|
||||
MinRatingSearch,
|
||||
MinResolutionSearch,
|
||||
OrientationSearch,
|
||||
ORSearchQuery,
|
||||
SearchQueryDTO,
|
||||
SearchQueryTypes,
|
||||
SomeOfSearchQuery,
|
||||
TextSearch,
|
||||
ToDateSearch
|
||||
} from '../../../src/common/entities/SearchQueryDTO';
|
||||
|
||||
describe('SearchQueryDTO', () => {
|
||||
|
||||
|
||||
const check = (query: SearchQueryDTO) => {
|
||||
expect(SearchQueryDTO.parse(SearchQueryDTO.stringify(query))).to.deep.equals(query, SearchQueryDTO.stringify(query));
|
||||
|
||||
};
|
||||
|
||||
describe('should serialize and deserialize', () => {
|
||||
it('Text search', () => {
|
||||
check(<TextSearch>{type: SearchQueryTypes.any_text, text: 'test'});
|
||||
check(<TextSearch>{type: SearchQueryTypes.person, text: 'person_test'});
|
||||
check(<TextSearch>{type: SearchQueryTypes.directory, text: 'directory'});
|
||||
check(<TextSearch>{type: SearchQueryTypes.keyword, text: 'big boom'});
|
||||
check(<TextSearch>{type: SearchQueryTypes.caption, text: 'caption'});
|
||||
check(<TextSearch>{type: SearchQueryTypes.file_name, text: 'filename'});
|
||||
check(<TextSearch>{type: SearchQueryTypes.position, text: 'New York'});
|
||||
});
|
||||
|
||||
it('Date search', () => {
|
||||
check(<FromDateSearch>{type: SearchQueryTypes.from_date, value: (new Date(2020, 1, 1)).getTime()});
|
||||
check(<ToDateSearch>{type: SearchQueryTypes.to_date, value: (new Date(2020, 1, 2)).getTime()});
|
||||
});
|
||||
it('Rating search', () => {
|
||||
check(<MinRatingSearch>{type: SearchQueryTypes.min_rating, value: 10});
|
||||
check(<MaxRatingSearch>{type: SearchQueryTypes.max_rating, value: 1});
|
||||
});
|
||||
it('Resolution search', () => {
|
||||
check(<MinResolutionSearch>{type: SearchQueryTypes.min_resolution, value: 10});
|
||||
check(<MaxResolutionSearch>{type: SearchQueryTypes.max_resolution, value: 5});
|
||||
});
|
||||
it('Distance search', () => {
|
||||
check(<DistanceSearch>{type: SearchQueryTypes.distance, distance: 10, from: {text: 'New York'}});
|
||||
});
|
||||
it('OrientationSearch search', () => {
|
||||
check(<OrientationSearch>{type: SearchQueryTypes.orientation, landscape: true});
|
||||
check(<OrientationSearch>{type: SearchQueryTypes.orientation, landscape: false});
|
||||
});
|
||||
it('And search', () => {
|
||||
check(<ANDSearchQuery>{
|
||||
type: SearchQueryTypes.AND,
|
||||
list: [
|
||||
<TextSearch>{type: SearchQueryTypes.keyword, text: 'big boom'},
|
||||
<TextSearch>{type: SearchQueryTypes.position, text: 'New York'}
|
||||
]
|
||||
});
|
||||
check(<ANDSearchQuery>{
|
||||
type: SearchQueryTypes.AND,
|
||||
list: [
|
||||
<TextSearch>{type: SearchQueryTypes.keyword, text: 'big boom'},
|
||||
<ANDSearchQuery>{
|
||||
type: SearchQueryTypes.AND,
|
||||
list: [
|
||||
<TextSearch>{type: SearchQueryTypes.caption, text: 'caption'},
|
||||
<TextSearch>{type: SearchQueryTypes.position, text: 'New York'}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
it('Or search', () => {
|
||||
check(<ORSearchQuery>{
|
||||
type: SearchQueryTypes.OR,
|
||||
list: [
|
||||
<TextSearch>{type: SearchQueryTypes.keyword, text: 'big boom'},
|
||||
<TextSearch>{type: SearchQueryTypes.position, text: 'New York'}
|
||||
]
|
||||
});
|
||||
check(<ORSearchQuery>{
|
||||
type: SearchQueryTypes.OR,
|
||||
list: [
|
||||
<ORSearchQuery>{
|
||||
type: SearchQueryTypes.OR,
|
||||
list: [
|
||||
<TextSearch>{type: SearchQueryTypes.keyword, text: 'big boom'},
|
||||
<TextSearch>{type: SearchQueryTypes.person, text: 'person_test'}
|
||||
]
|
||||
},
|
||||
<TextSearch>{type: SearchQueryTypes.position, text: 'New York'}
|
||||
]
|
||||
});
|
||||
});
|
||||
it('Some of search', () => {
|
||||
check(<SomeOfSearchQuery>{
|
||||
type: SearchQueryTypes.SOME_OF,
|
||||
list: [
|
||||
<TextSearch>{type: SearchQueryTypes.keyword, text: 'big boom'},
|
||||
<TextSearch>{type: SearchQueryTypes.position, text: 'New York'}
|
||||
]
|
||||
});
|
||||
check(<SomeOfSearchQuery>{
|
||||
type: SearchQueryTypes.SOME_OF,
|
||||
min: 2,
|
||||
list: [
|
||||
<TextSearch>{type: SearchQueryTypes.keyword, text: 'big boom'},
|
||||
<TextSearch>{type: SearchQueryTypes.position, text: 'New York'},
|
||||
<TextSearch>{type: SearchQueryTypes.caption, text: 'caption test'}
|
||||
]
|
||||
});
|
||||
check(<SomeOfSearchQuery>{
|
||||
type: SearchQueryTypes.SOME_OF,
|
||||
min: 2,
|
||||
list: [
|
||||
<TextSearch>{type: SearchQueryTypes.keyword, text: 'big boom'},
|
||||
<TextSearch>{type: SearchQueryTypes.person, text: 'person_test'},
|
||||
<ANDSearchQuery>{
|
||||
type: SearchQueryTypes.AND,
|
||||
list: [
|
||||
<TextSearch>{type: SearchQueryTypes.caption, text: 'caption'},
|
||||
<TextSearch>{type: SearchQueryTypes.position, text: 'New York'}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
Loading…
Reference in New Issue
Block a user