From 8afd49c588e471e7ad1545cf8b4de1b859373d84 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sat, 20 Mar 2021 14:37:56 +0100 Subject: [PATCH] adding minimal autocomplete --- src/common/entities/AutoCompleteItem.ts | 2 +- src/frontend/app/app.module.ts | 4 ++ .../app/pipes/StringifySearchQuery.ts | 7 ++- .../random-query-builder.gallery.component.ts | 6 ++- .../ui/gallery/search/autocomplete.service.ts | 36 ++++++++++--- .../search/search-query-parser.service.ts | 46 +++++++++++++++++ .../search/search.gallery.component.css | 16 ++++-- .../search/search.gallery.component.html | 16 +++++- .../search/search.gallery.component.ts | 50 +++++++++++++++---- 9 files changed, 157 insertions(+), 26 deletions(-) create mode 100644 src/frontend/app/ui/gallery/search/search-query-parser.service.ts diff --git a/src/common/entities/AutoCompleteItem.ts b/src/common/entities/AutoCompleteItem.ts index f4180aec..29bf9995 100644 --- a/src/common/entities/AutoCompleteItem.ts +++ b/src/common/entities/AutoCompleteItem.ts @@ -1,7 +1,7 @@ import {SearchQueryTypes} from './SearchQueryDTO'; export class AutoCompleteItem { - constructor(public text: string, public type: SearchQueryTypes) { + constructor(public text: string, public type: SearchQueryTypes = null) { } equals(other: AutoCompleteItem) { diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index acae9919..35045f85 100644 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -95,6 +95,8 @@ import {CSRFInterceptor} from './model/network/helper/csrf.interceptor'; import {SettingsEntryComponent} from './ui/settings/_abstract/settings-entry/settings-entry.component'; import {GallerySearchQueryEntryComponent} from './ui/gallery/search/query-enrty/query-entry.search.gallery.component'; import {StringifySearchQuery} from './pipes/StringifySearchQuery'; +import {AutoCompleteService} from './ui/gallery/search/autocomplete.service'; +import {SearchQueryParserService} from './ui/gallery/search/search-query-parser.service'; @Injectable() @@ -231,6 +233,8 @@ export function translationsFactory(locale: string) { GalleryCacheService, GalleryService, MapService, + SearchQueryParserService, + AutoCompleteService, AuthenticationService, ThumbnailLoaderService, ThumbnailManagerService, diff --git a/src/frontend/app/pipes/StringifySearchQuery.ts b/src/frontend/app/pipes/StringifySearchQuery.ts index a5ec40b4..0a6768d7 100644 --- a/src/frontend/app/pipes/StringifySearchQuery.ts +++ b/src/frontend/app/pipes/StringifySearchQuery.ts @@ -1,11 +1,16 @@ import {Pipe, PipeTransform} from '@angular/core'; import {SearchQueryDTO} from '../../../common/entities/SearchQueryDTO'; +import {SearchQueryParserService} from '../ui/gallery/search/search-query-parser.service'; @Pipe({name: 'searchQuery'}) export class StringifySearchQuery implements PipeTransform { + constructor( + private _searchQueryParserService: SearchQueryParserService) { + } + transform(query: SearchQueryDTO): string { - return SearchQueryDTO.stringify(query); + return this._searchQueryParserService.stringify(query); } } diff --git a/src/frontend/app/ui/gallery/random-query-builder/random-query-builder.gallery.component.ts b/src/frontend/app/ui/gallery/random-query-builder/random-query-builder.gallery.component.ts index 0f72a0bb..557f6c7f 100644 --- a/src/frontend/app/ui/gallery/random-query-builder/random-query-builder.gallery.component.ts +++ b/src/frontend/app/ui/gallery/random-query-builder/random-query-builder.gallery.component.ts @@ -11,6 +11,7 @@ import {Subscription} from 'rxjs'; import {SearchQueryDTO, SearchQueryTypes, TextSearch} from '../../../../../common/entities/SearchQueryDTO'; import {ActivatedRoute, Params} from '@angular/router'; import {QueryParams} from '../../../../../common/QueryParams'; +import {SearchQueryParserService} from '../search/search-query-parser.service'; @Component({ @@ -34,6 +35,7 @@ export class RandomQueryBuilderGalleryComponent implements OnInit, OnDestroy { constructor(public _galleryService: GalleryService, private _notification: NotificationService, + private _searchQueryParserService: SearchQueryParserService, public i18n: I18n, private _route: ActivatedRoute, private modalService: BsModalService) { @@ -57,7 +59,7 @@ export class RandomQueryBuilderGalleryComponent implements OnInit, OnDestroy { validateRawSearchText() { try { - this.searchQueryDTO = SearchQueryDTO.parse(this.rawSearchText); + this.searchQueryDTO = this._searchQueryParserService.parse(this.rawSearchText); this.url = NetworkService.buildUrl(Config.Client.publicUrl + '/api/gallery/random/' + this.HTMLSearchQuery); } catch (e) { console.error(e); @@ -65,7 +67,7 @@ export class RandomQueryBuilderGalleryComponent implements OnInit, OnDestroy { } onQueryChange() { - this.rawSearchText = SearchQueryDTO.stringify(this.searchQueryDTO); + this.rawSearchText = this._searchQueryParserService.stringify(this.searchQueryDTO); this.url = NetworkService.buildUrl(Config.Client.publicUrl + '/api/gallery/random/' + this.HTMLSearchQuery); } diff --git a/src/frontend/app/ui/gallery/search/autocomplete.service.ts b/src/frontend/app/ui/gallery/search/autocomplete.service.ts index b458d582..9aa45213 100644 --- a/src/frontend/app/ui/gallery/search/autocomplete.service.ts +++ b/src/frontend/app/ui/gallery/search/autocomplete.service.ts @@ -2,23 +2,47 @@ import {Injectable} from '@angular/core'; import {NetworkService} from '../../../model/network/network.service'; import {AutoCompleteItem} from '../../../../../common/entities/AutoCompleteItem'; import {GalleryCacheService} from '../cache.gallery.service'; +import {SearchQueryParserService} from './search-query-parser.service'; +import {BehaviorSubject} from 'rxjs'; @Injectable() export class AutoCompleteService { constructor(private _networkService: NetworkService, - private galleryCacheService: GalleryCacheService) { + private _searchQueryParserService: SearchQueryParserService, + private _galleryCacheService: GalleryCacheService) { } - public async autoComplete(text: string): Promise> { - let items: Array = this.galleryCacheService.getAutoComplete(text); - if (items == null) { - items = await this._networkService.getJson>('/autocomplete/' + text); - this.galleryCacheService.setAutoComplete(text, items); + public autoComplete(text: string): BehaviorSubject { + const items: BehaviorSubject = new BehaviorSubject( + this.sortResults(text, this.getQueryKeywords(text))); + const cached = this._galleryCacheService.getAutoComplete(text); + if (cached == null) { + this._networkService.getJson('/autocomplete/' + text).then(ret => { + this._galleryCacheService.setAutoComplete(text, ret); + items.next(this.sortResults(text, ret.concat(items.value))); + }); } return items; } + private sortResults(text: string, items: AutoCompleteItem[]) { + return items.sort((a, b) => { + if ((a.text.startsWith(text) && b.text.startsWith(text)) || + (!a.text.startsWith(text) && !b.text.startsWith(text))) { + return a.text.localeCompare(b.text); + } else if (a.text.startsWith(text)) { + return -1; + } + return 1; + }); + } + + private getQueryKeywords(text: string) { + return Object.values(this._searchQueryParserService.keywords) + .filter(key => key.startsWith(text)) + .map(key => new AutoCompleteItem(key + ':')); + } } diff --git a/src/frontend/app/ui/gallery/search/search-query-parser.service.ts b/src/frontend/app/ui/gallery/search/search-query-parser.service.ts new file mode 100644 index 00000000..4bfdcd99 --- /dev/null +++ b/src/frontend/app/ui/gallery/search/search-query-parser.service.ts @@ -0,0 +1,46 @@ +import {Injectable} from '@angular/core'; +import {QueryKeywords, SearchQueryParser} from '../../../../../common/SearchQueryParser'; +import {SearchQueryDTO} from '../../../../../common/entities/SearchQueryDTO'; + +@Injectable() +export class SearchQueryParserService { + + public readonly keywords: QueryKeywords = { + NSomeOf: '-of', + and: 'and', + caption: 'caption', + directory: 'directory', + file_name: 'file-name', + from: 'from', + keyword: 'keyword', + landscape: 'landscape', + maxRating: 'max-rating', + maxResolution: 'max-resolution', + minRating: 'min-rating', + minResolution: 'min-resolution', + or: 'or', + orientation: 'orientation', + person: 'person', + portrait: 'portrait', + position: 'position', + someOf: 'some-of', + to: 'to', + kmFrom: 'km-from' + }; + private readonly parser: SearchQueryParser; + + constructor() { + this.parser = new SearchQueryParser(this.keywords); + } + + + public parse(str: string): SearchQueryDTO { + return this.parser.parse(str); + } + + stringify(query: SearchQueryDTO): string { + return this.parser.stringify(query); + } + + +} diff --git a/src/frontend/app/ui/gallery/search/search.gallery.component.css b/src/frontend/app/ui/gallery/search/search.gallery.component.css index 1a9e3cc9..bf67691c 100644 --- a/src/frontend/app/ui/gallery/search/search.gallery.component.css +++ b/src/frontend/app/ui/gallery/search/search.gallery.component.css @@ -36,9 +36,19 @@ text-decoration: none; } -#srch-term { - border-bottom-left-radius: 0; - border-bottom-width: 0; +.search-text { + border: 0; + z-index: 6; + width: 500px; + background: transparent; +} + +.search-hint { + width: 500px; + background: white; + z-index: 1; + position: absolute; + border: 0; } form { diff --git a/src/frontend/app/ui/gallery/search/search.gallery.component.html b/src/frontend/app/ui/gallery/search/search.gallery.component.html index cc659c23..2b2c2008 100644 --- a/src/frontend/app/ui/gallery/search/search.gallery.component.html +++ b/src/frontend/app/ui/gallery/search/search.gallery.component.html @@ -1,7 +1,7 @@