mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +08:00
Adding date pattern UI #660
This commit is contained in:
parent
4cb986c530
commit
37641ff02e
@ -10,6 +10,8 @@ import {Brackets, SelectQueryBuilder, WhereExpression} from 'typeorm';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {
|
||||
ANDSearchQuery,
|
||||
DatePatternFrequency,
|
||||
DatePatternSearch,
|
||||
DistanceSearch,
|
||||
FromDateSearch,
|
||||
MaxRatingSearch,
|
||||
@ -654,6 +656,105 @@ export class SearchManager {
|
||||
return q;
|
||||
});
|
||||
|
||||
case SearchQueryTypes.date_pattern: {
|
||||
const tq = query as DatePatternSearch;
|
||||
|
||||
return new Brackets((q): unknown => {
|
||||
// Fixed frequency
|
||||
if ((tq.frequency === DatePatternFrequency.years_ago ||
|
||||
tq.frequency === DatePatternFrequency.months_ago ||
|
||||
tq.frequency === DatePatternFrequency.weeks_ago ||
|
||||
tq.frequency === DatePatternFrequency.days_ago)) {
|
||||
|
||||
if (!tq.agoNumber) {
|
||||
throw new Error('ago number is missing on date patter search query with frequency: ' + DatePatternFrequency[tq.frequency]);
|
||||
}
|
||||
const to = new Date();
|
||||
to.setHours(0, 0, 0, 0);
|
||||
to.setUTCDate(to.getUTCDate() + 1);
|
||||
|
||||
switch (tq.frequency) {
|
||||
case DatePatternFrequency.days_ago:
|
||||
to.setUTCDate(to.getUTCDate() - tq.agoNumber);
|
||||
break;
|
||||
case DatePatternFrequency.weeks_ago:
|
||||
to.setUTCDate(to.getUTCDate() - tq.agoNumber * 7);
|
||||
break;
|
||||
|
||||
case DatePatternFrequency.months_ago:
|
||||
to.setUTCMonth(to.getUTCMonth() - tq.agoNumber);
|
||||
break;
|
||||
|
||||
case DatePatternFrequency.years_ago:
|
||||
to.setUTCFullYear(to.getUTCFullYear() - tq.agoNumber);
|
||||
break;
|
||||
}
|
||||
const from = new Date(to);
|
||||
from.setUTCDate(from.getUTCDate() - tq.daysLength);
|
||||
|
||||
const textParam: { [key: string]: unknown } = {};
|
||||
textParam['to' + queryId] = to.getTime();
|
||||
textParam['from' + queryId] = from.getTime();
|
||||
if (tq.negate) {
|
||||
|
||||
q.where(
|
||||
`media.metadata.creationDate >= :to${queryId}`,
|
||||
textParam
|
||||
).orWhere(`media.metadata.creationDate < :from${queryId}`,
|
||||
textParam);
|
||||
} else {
|
||||
q.where(
|
||||
`media.metadata.creationDate < :to${queryId}`,
|
||||
textParam
|
||||
).andWhere(`media.metadata.creationDate >= :from${queryId}`,
|
||||
textParam);
|
||||
}
|
||||
|
||||
} else {
|
||||
// recurring
|
||||
|
||||
const textParam: { [key: string]: unknown } = {};
|
||||
textParam['diff' + queryId] = tq.daysLength;
|
||||
|
||||
const relationTop = tq.negate ? '>' : '<=';
|
||||
const relationBottom = tq.negate ? '<=' : '>';
|
||||
switch (tq.frequency) {
|
||||
case DatePatternFrequency.every_year:
|
||||
if(tq.daysLength >= 365){
|
||||
return q;
|
||||
}
|
||||
q.where(
|
||||
`CAST(strftime('%j',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('%j','now') AS INTEGER)`
|
||||
).andWhere(`CAST(strftime('%j',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('%j','now','-:diff${queryId} day') AS INTEGER)`,
|
||||
textParam);
|
||||
break;
|
||||
case DatePatternFrequency.every_month:
|
||||
if(tq.daysLength >=31){
|
||||
return q;
|
||||
}
|
||||
q.where(
|
||||
`CAST(strftime('%d',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('%d','now') AS INTEGER)`
|
||||
).andWhere(`CAST(strftime('%d',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('%d','now','-:diff${queryId} day') AS INTEGER)`,
|
||||
textParam);
|
||||
break;
|
||||
case DatePatternFrequency.every_week:
|
||||
if(tq.daysLength >= 7){
|
||||
return q;
|
||||
}
|
||||
q.where(
|
||||
`CAST(strftime('%w',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('%w','now') AS INTEGER)`
|
||||
).andWhere(`CAST(strftime('%w',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('%w','now','-:diff${queryId} day') AS INTEGER)`,
|
||||
textParam);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return q;
|
||||
});
|
||||
}
|
||||
|
||||
case SearchQueryTypes.SOME_OF:
|
||||
throw new Error('Some of not supported');
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
import {Utils} from './Utils';
|
||||
|
||||
export interface QueryKeywords {
|
||||
days_ago: any;
|
||||
days_ago: string;
|
||||
years_ago: string;
|
||||
months_ago: string;
|
||||
weeks_ago: string;
|
||||
@ -572,6 +572,7 @@ export class SearchQueryParser {
|
||||
);
|
||||
case SearchQueryTypes.date_pattern: {
|
||||
const q = (query as DatePatternSearch);
|
||||
q.daysLength = q.daysLength || 0;
|
||||
let strBuilder = '';
|
||||
if (q.daysLength <= 0) {
|
||||
strBuilder += this.keywords.sameDay;
|
||||
@ -590,16 +591,16 @@ export class SearchQueryParser {
|
||||
strBuilder += this.keywords.every_year;
|
||||
break;
|
||||
case DatePatternFrequency.days_ago:
|
||||
strBuilder += this.keywords.days_ago.replace(/%d/g, q.agoNumber.toString());
|
||||
strBuilder += this.keywords.days_ago.replace(/%d/g, (q.agoNumber || 0).toString());
|
||||
break;
|
||||
case DatePatternFrequency.weeks_ago:
|
||||
strBuilder += this.keywords.weeks_ago.replace(/%d/g, q.agoNumber.toString());
|
||||
strBuilder += this.keywords.weeks_ago.replace(/%d/g, (q.agoNumber || 0).toString());
|
||||
break;
|
||||
case DatePatternFrequency.months_ago:
|
||||
strBuilder += this.keywords.months_ago.replace(/%d/g, q.agoNumber.toString());
|
||||
strBuilder += this.keywords.months_ago.replace(/%d/g, (q.agoNumber || 0).toString());
|
||||
break;
|
||||
case DatePatternFrequency.years_ago:
|
||||
strBuilder += this.keywords.years_ago.replace(/%d/g, q.agoNumber.toString());
|
||||
strBuilder += this.keywords.years_ago.replace(/%d/g, (q.agoNumber || 0).toString());
|
||||
break;
|
||||
}
|
||||
return strBuilder;
|
||||
|
@ -231,10 +231,11 @@ export enum DatePatternFrequency {
|
||||
days_ago = 10, weeks_ago, months_ago, years_ago
|
||||
}
|
||||
|
||||
export interface DatePatternSearch {
|
||||
export interface DatePatternSearch extends NegatableSearchQuery {
|
||||
type: SearchQueryTypes.date_pattern;
|
||||
daysLength: number; // days
|
||||
frequency: DatePatternFrequency;
|
||||
agoNumber?: number;
|
||||
negate?: boolean;
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,7 @@ EnumTranslations[SearchQueryTypes[SearchQueryTypes.directory]] = $localize`Direc
|
||||
EnumTranslations[SearchQueryTypes[SearchQueryTypes.file_name]] = $localize`File name`;
|
||||
EnumTranslations[SearchQueryTypes[SearchQueryTypes.caption]] = $localize`Caption`;
|
||||
EnumTranslations[SearchQueryTypes[SearchQueryTypes.orientation]] = $localize`Orientation`;
|
||||
EnumTranslations[SearchQueryTypes[SearchQueryTypes.date_pattern]] = $localize`Date pattern`;
|
||||
EnumTranslations[SearchQueryTypes[SearchQueryTypes.position]] = $localize`Position`;
|
||||
EnumTranslations[SearchQueryTypes[SearchQueryTypes.person]] = $localize`Person`;
|
||||
EnumTranslations[SearchQueryTypes[SearchQueryTypes.keyword]] = $localize`Keyword`;
|
||||
|
@ -213,6 +213,55 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="SearchQueryTypes.date_pattern" class="col-10 col-lg d-flex">
|
||||
|
||||
<div class="row">
|
||||
<div class="input-group col-md-6">
|
||||
<span class="input-group-text" i18n>Last</span>
|
||||
<input id="daysLength"
|
||||
name="daysLength"
|
||||
title="Last N Days"
|
||||
placeholder="1"
|
||||
i18n-title
|
||||
[min]="0"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsDatePatternQuery.daysLength"
|
||||
(ngModelChange)="onChange()"
|
||||
type="number">
|
||||
<span class="input-group-text" i18n>days</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group col-md-6">
|
||||
<input
|
||||
*ngIf="AsDatePatternQuery.frequency == DatePatternFrequency.days_ago || AsDatePatternQuery.frequency == DatePatternFrequency.weeks_ago || AsDatePatternQuery.frequency == DatePatternFrequency.months_ago || AsDatePatternQuery.frequency == DatePatternFrequency.years_ago"
|
||||
id="agoNumber"
|
||||
name="agoNumber"
|
||||
title="Ago"
|
||||
placeholder="1"
|
||||
i18n-title
|
||||
[min]="0"
|
||||
class="form-control input-md"
|
||||
[(ngModel)]="AsDatePatternQuery.agoNumber"
|
||||
(ngModelChange)="onChange()"
|
||||
type="number">
|
||||
<select class="form-select rounded-2"
|
||||
[(ngModel)]="AsDatePatternQuery.frequency"
|
||||
(ngModelChange)="onChange()"
|
||||
id="date_pattern-select"
|
||||
name="date_pattern-select"
|
||||
title="Date Pattern"
|
||||
required>
|
||||
<option [ngValue]="DatePatternFrequency.days_ago" i18n>Day(s) ago</option>
|
||||
<option [ngValue]="DatePatternFrequency.weeks_ago" i18n>Week(s) ago</option>
|
||||
<option [ngValue]="DatePatternFrequency.months_ago" i18n>Month(s) ago</option>
|
||||
<option [ngValue]="DatePatternFrequency.years_ago" i18n>Year(s) ago</option>
|
||||
<option [ngValue]="DatePatternFrequency.every_week" i18n>Every week</option>
|
||||
<option [ngValue]="DatePatternFrequency.every_month" i18n>Every Month</option>
|
||||
<option [ngValue]="DatePatternFrequency.every_year" i18n>Every year</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="col-2 col-lg-1 align-self-center">
|
||||
<button [ngClass]="'btn-danger'"
|
||||
|
@ -1,5 +1,7 @@
|
||||
import {Component, EventEmitter, forwardRef, Output} from '@angular/core';
|
||||
import {
|
||||
DatePatternFrequency,
|
||||
DatePatternSearch,
|
||||
DistanceSearch,
|
||||
ListSearchQueryTypes,
|
||||
OrientationSearch,
|
||||
@ -37,6 +39,7 @@ export class GallerySearchQueryEntryComponent
|
||||
public queryEntry: SearchQueryDTO;
|
||||
public SearchQueryTypesEnum: { value: string; key: SearchQueryTypes }[];
|
||||
public SearchQueryTypes = SearchQueryTypes;
|
||||
public DatePatternFrequency = DatePatternFrequency;
|
||||
public TextSearchQueryMatchTypes = TextSearchQueryMatchTypes;
|
||||
@Output() delete = new EventEmitter<void>();
|
||||
|
||||
@ -72,6 +75,10 @@ export class GallerySearchQueryEntryComponent
|
||||
return this.queryEntry as OrientationSearch;
|
||||
}
|
||||
|
||||
get AsDatePatternQuery(): DatePatternSearch {
|
||||
return this.queryEntry as DatePatternSearch;
|
||||
}
|
||||
|
||||
get AsDistanceQuery(): DistanceSearch {
|
||||
return this.queryEntry as DistanceSearch;
|
||||
}
|
||||
@ -162,6 +169,16 @@ export class GallerySearchQueryEntryComponent
|
||||
} else {
|
||||
delete this.AsOrientationQuery.landscape;
|
||||
}
|
||||
|
||||
|
||||
if (this.queryEntry.type === SearchQueryTypes.date_pattern) {
|
||||
this.AsDatePatternQuery.daysLength = 0;
|
||||
this.AsDatePatternQuery.frequency = DatePatternFrequency.every_year;
|
||||
} else {
|
||||
delete this.AsDatePatternQuery.daysLength;
|
||||
delete this.AsDatePatternQuery.frequency;
|
||||
delete this.AsDatePatternQuery.agoNumber;
|
||||
}
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,16 @@ export class SearchQueryParserService {
|
||||
minResolution: 'min-resolution',
|
||||
orientation: 'orientation',
|
||||
|
||||
years_ago: '%d-years-ago',
|
||||
months_ago: '%d-months-ago',
|
||||
weeks_ago: '%d-weeks-ago',
|
||||
days_ago: '%d-days-ago',
|
||||
every_year: 'every-year',
|
||||
every_month: 'every-month',
|
||||
every_week: 'every-week',
|
||||
lastNDays: 'last-%d-days',
|
||||
sameDay: 'same-day',
|
||||
|
||||
any_text: 'any-text',
|
||||
keyword: 'keyword',
|
||||
caption: 'caption',
|
||||
|
@ -5,6 +5,8 @@ import {Utils} from '../../../../../src/common/Utils';
|
||||
import {DBTestHelper} from '../../../DBTestHelper';
|
||||
import {
|
||||
ANDSearchQuery,
|
||||
DatePatternFrequency,
|
||||
DatePatternSearch,
|
||||
DistanceSearch,
|
||||
FromDateSearch,
|
||||
MaxRatingSearch,
|
||||
@ -106,10 +108,14 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
|
||||
subDir = TestHelper.getDirectoryEntry(directory, 'The Phantom Menace');
|
||||
subDir2 = TestHelper.getDirectoryEntry(directory, 'Return of the Jedi');
|
||||
p = TestHelper.getPhotoEntry1(directory);
|
||||
p.metadata.creationDate = Date.now();
|
||||
p2 = TestHelper.getPhotoEntry2(directory);
|
||||
p2.metadata.creationDate = Date.now() - 60 * 60 * 24 * 1000;
|
||||
v = TestHelper.getVideoEntry1(directory);
|
||||
v.metadata.creationDate = Date.now() - 60 * 60 * 24 * 7 * 1000;
|
||||
gpx = TestHelper.getRandomizedGPXEntry(directory);
|
||||
p4 = TestHelper.getPhotoEntry4(subDir2);
|
||||
p4.metadata.creationDate = Date.now() - 60 * 60 * 24 * 366 * 1000;
|
||||
const pFaceLessTmp = TestHelper.getPhotoEntry3(subDir);
|
||||
delete pFaceLessTmp.metadata.faces;
|
||||
|
||||
@ -876,14 +882,14 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
|
||||
} as SearchResultDTO));
|
||||
|
||||
query = ({
|
||||
value: p.metadata.creationDate, type: SearchQueryTypes.from_date
|
||||
value: p2.metadata.creationDate, type: SearchQueryTypes.from_date
|
||||
} as FromDateSearch);
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir({
|
||||
searchQuery: query,
|
||||
directories: [],
|
||||
media: [p, v],
|
||||
media: [p, p2],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
} as SearchResultDTO));
|
||||
@ -898,7 +904,7 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
|
||||
.to.deep.equalInAnyOrder(removeDir({
|
||||
searchQuery: query,
|
||||
directories: [],
|
||||
media: [p2, pFaceLess, p4],
|
||||
media: [p2, pFaceLess, p4, v],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
} as SearchResultDTO));
|
||||
@ -919,6 +925,104 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
|
||||
|
||||
});
|
||||
|
||||
it('should search date pattern', async () => {
|
||||
const sm = new SearchManager();
|
||||
|
||||
let query: DatePatternSearch = {
|
||||
daysLength: 0,
|
||||
frequency: DatePatternFrequency.every_year,
|
||||
type: SearchQueryTypes.date_pattern
|
||||
};
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir({
|
||||
searchQuery: query,
|
||||
directories: [],
|
||||
media: [],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
} as SearchResultDTO));
|
||||
|
||||
|
||||
query = {
|
||||
daysLength: 1,
|
||||
frequency: DatePatternFrequency.every_year,
|
||||
type: SearchQueryTypes.date_pattern
|
||||
} as DatePatternSearch;
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir({
|
||||
searchQuery: query,
|
||||
directories: [],
|
||||
media: [p],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
} as SearchResultDTO));
|
||||
|
||||
query = {
|
||||
daysLength: 2,
|
||||
frequency: DatePatternFrequency.every_year,
|
||||
type: SearchQueryTypes.date_pattern
|
||||
} as DatePatternSearch;
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir({
|
||||
searchQuery: query,
|
||||
directories: [],
|
||||
media: [p, p2, p4],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
} as SearchResultDTO));
|
||||
|
||||
|
||||
query = {
|
||||
daysLength: 1,
|
||||
agoNumber: 10,
|
||||
frequency: DatePatternFrequency.days_ago,
|
||||
type: SearchQueryTypes.date_pattern
|
||||
} as DatePatternSearch;
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir({
|
||||
searchQuery: query,
|
||||
directories: [],
|
||||
media: [],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
} as SearchResultDTO));
|
||||
|
||||
query = {
|
||||
daysLength: 366,
|
||||
frequency: DatePatternFrequency.every_year,
|
||||
type: SearchQueryTypes.date_pattern
|
||||
} as DatePatternSearch;
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir({
|
||||
searchQuery: query,
|
||||
directories: [],
|
||||
media: [p, p2, p4, pFaceLess, v],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
} as SearchResultDTO));
|
||||
|
||||
query = {
|
||||
daysLength: 32,
|
||||
frequency: DatePatternFrequency.every_month,
|
||||
type: SearchQueryTypes.date_pattern
|
||||
} as DatePatternSearch;
|
||||
|
||||
expect(Utils.clone(await sm.search(query)))
|
||||
.to.deep.equalInAnyOrder(removeDir({
|
||||
searchQuery: query,
|
||||
directories: [],
|
||||
media: [p, p2, p4, pFaceLess, v],
|
||||
metaFile: [],
|
||||
resultOverflow: false
|
||||
} as SearchResultDTO));
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should search rating', async () => {
|
||||
const sm = new SearchManager();
|
||||
|
Loading…
x
Reference in New Issue
Block a user