1
0
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:
Patrik J. Braun 2023-06-20 00:06:37 +02:00
parent 4cb986c530
commit 37641ff02e
8 changed files with 293 additions and 9 deletions

View File

@ -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');
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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`;

View File

@ -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'"

View File

@ -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();
}

View File

@ -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',

View File

@ -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();