mirror of
https://github.com/xuthus83/pigallery2.git
synced 2024-11-03 21:04:03 +08:00
parent
8afd49c588
commit
6b24436c22
@ -1,6 +1,11 @@
|
||||
import {SearchQueryTypes} from './SearchQueryDTO';
|
||||
|
||||
export class AutoCompleteItem {
|
||||
export interface IAutoCompleteItem {
|
||||
text: string;
|
||||
type?: SearchQueryTypes;
|
||||
}
|
||||
|
||||
export class AutoCompleteItem implements IAutoCompleteItem {
|
||||
constructor(public text: string, public type: SearchQueryTypes = null) {
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import {Injectable} from '@angular/core';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
import {Utils} from '../../../../common/Utils';
|
||||
import {Config} from '../../../../common/config/public/Config';
|
||||
import {AutoCompleteItem} from '../../../../common/entities/AutoCompleteItem';
|
||||
import {AutoCompleteItem, IAutoCompleteItem} from '../../../../common/entities/AutoCompleteItem';
|
||||
import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
|
||||
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||
import {SortingMethods} from '../../../../common/entities/SortingMethods';
|
||||
@ -101,14 +101,14 @@ export class GalleryCacheService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getAutoComplete(text: string): AutoCompleteItem[] {
|
||||
public getAutoComplete(text: string): IAutoCompleteItem[] {
|
||||
if (Config.Client.Other.enableCache === false) {
|
||||
return null;
|
||||
}
|
||||
const key = GalleryCacheService.AUTO_COMPLETE_PREFIX + text;
|
||||
const tmp = localStorage.getItem(key);
|
||||
if (tmp != null) {
|
||||
const value: CacheItem<AutoCompleteItem[]> = JSON.parse(tmp);
|
||||
const value: CacheItem<IAutoCompleteItem[]> = JSON.parse(tmp);
|
||||
if (value.timestamp < Date.now() - Config.Client.Search.AutoComplete.cacheTimeout) {
|
||||
localStorage.removeItem(key);
|
||||
return null;
|
||||
@ -118,11 +118,11 @@ export class GalleryCacheService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public setAutoComplete(text: string, items: Array<AutoCompleteItem>): void {
|
||||
public setAutoComplete(text: string, items: Array<IAutoCompleteItem>): void {
|
||||
if (Config.Client.Other.enableCache === false) {
|
||||
return;
|
||||
}
|
||||
const tmp: CacheItem<Array<AutoCompleteItem>> = {
|
||||
const tmp: CacheItem<Array<IAutoCompleteItem>> = {
|
||||
timestamp: Date.now(),
|
||||
item: items
|
||||
};
|
||||
|
@ -1,33 +1,66 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {NetworkService} from '../../../model/network/network.service';
|
||||
import {AutoCompleteItem} from '../../../../../common/entities/AutoCompleteItem';
|
||||
import {IAutoCompleteItem} from '../../../../../common/entities/AutoCompleteItem';
|
||||
import {GalleryCacheService} from '../cache.gallery.service';
|
||||
import {SearchQueryParserService} from './search-query-parser.service';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {SearchQueryTypes, TextSearchQueryTypes} from '../../../../../common/entities/SearchQueryDTO';
|
||||
|
||||
@Injectable()
|
||||
export class AutoCompleteService {
|
||||
|
||||
private keywords: string[] = [];
|
||||
|
||||
constructor(private _networkService: NetworkService,
|
||||
private _searchQueryParserService: SearchQueryParserService,
|
||||
private _galleryCacheService: GalleryCacheService) {
|
||||
this.keywords = Object.values(this._searchQueryParserService.keywords)
|
||||
.filter(k => k !== this._searchQueryParserService.keywords.or &&
|
||||
k !== this._searchQueryParserService.keywords.and &&
|
||||
k !== this._searchQueryParserService.keywords.portrait &&
|
||||
k !== this._searchQueryParserService.keywords.kmFrom &&
|
||||
k !== this._searchQueryParserService.keywords.NSomeOf)
|
||||
.map(k => k + ':');
|
||||
|
||||
this.keywords.push(this._searchQueryParserService.keywords.and);
|
||||
this.keywords.push(this._searchQueryParserService.keywords.or);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.keywords.push(i + this._searchQueryParserService.keywords.NSomeOf);
|
||||
}
|
||||
}
|
||||
|
||||
public autoComplete(text: string): BehaviorSubject<AutoCompleteItem[]> {
|
||||
const items: BehaviorSubject<AutoCompleteItem[]> = new BehaviorSubject(
|
||||
public autoComplete(text: string): BehaviorSubject<RenderableAutoCompleteItem[]> {
|
||||
const items: BehaviorSubject<RenderableAutoCompleteItem[]> = new BehaviorSubject(
|
||||
this.sortResults(text, this.getQueryKeywords(text)));
|
||||
const cached = this._galleryCacheService.getAutoComplete(text);
|
||||
if (cached == null) {
|
||||
this._networkService.getJson<AutoCompleteItem[]>('/autocomplete/' + text).then(ret => {
|
||||
this._networkService.getJson<IAutoCompleteItem[]>('/autocomplete/' + text).then(ret => {
|
||||
this._galleryCacheService.setAutoComplete(text, ret);
|
||||
items.next(this.sortResults(text, ret.concat(items.value)));
|
||||
items.next(this.sortResults(text, ret.map(i => this.ACItemToRenderable(i)).concat(items.value)));
|
||||
});
|
||||
} else {
|
||||
items.next(this.sortResults(text, cached.map(i => this.ACItemToRenderable(i)).concat(items.value)));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private sortResults(text: string, items: AutoCompleteItem[]) {
|
||||
private ACItemToRenderable(item: IAutoCompleteItem): RenderableAutoCompleteItem {
|
||||
if (!item.type) {
|
||||
return {text: item.text, queryHint: item.text};
|
||||
}
|
||||
if (TextSearchQueryTypes.includes(item.type) && item.type !== SearchQueryTypes.any_text) {
|
||||
return {
|
||||
text: item.text, type: item.type,
|
||||
queryHint:
|
||||
(<any>this._searchQueryParserService.keywords)[SearchQueryTypes[item.type]] + ':(' + item.text + ')'
|
||||
};
|
||||
}
|
||||
return {
|
||||
text: item.text, type: item.type, queryHint: item.text
|
||||
};
|
||||
}
|
||||
|
||||
private sortResults(text: string, items: RenderableAutoCompleteItem[]) {
|
||||
return items.sort((a, b) => {
|
||||
if ((a.text.startsWith(text) && b.text.startsWith(text)) ||
|
||||
(!a.text.startsWith(text) && !b.text.startsWith(text))) {
|
||||
@ -40,9 +73,16 @@ export class AutoCompleteService {
|
||||
|
||||
}
|
||||
|
||||
private getQueryKeywords(text: string) {
|
||||
return Object.values(this._searchQueryParserService.keywords)
|
||||
private getQueryKeywords(text: string): RenderableAutoCompleteItem[] {
|
||||
return this.keywords
|
||||
.filter(key => key.startsWith(text))
|
||||
.map(key => new AutoCompleteItem(key + ':'));
|
||||
.map(key => ({
|
||||
text: key,
|
||||
queryHint: key
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export interface RenderableAutoCompleteItem extends IAutoCompleteItem {
|
||||
queryHint: string;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
padding: 5px 0;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||
z-index: 7;
|
||||
}
|
||||
|
||||
.autocomplete-item {
|
||||
|
@ -30,9 +30,9 @@
|
||||
autocomplete="off">
|
||||
|
||||
|
||||
<div class="autocomplete-list" *ngIf="autoCompleteItems.length > 0"
|
||||
<div class="autocomplete-list" *ngIf="autoCompleteRenders.length > 0"
|
||||
(mouseover)="setMouseOverAutoComplete(true)" (mouseout)="setMouseOverAutoComplete(false)">
|
||||
<div class="autocomplete-item" *ngFor="let item of autoCompleteItems">
|
||||
<div class="autocomplete-item" (click)="applyAutoComplete(item)" *ngFor="let item of autoCompleteRenders">
|
||||
<div>
|
||||
<span [ngSwitch]="item.type">
|
||||
<span *ngSwitchCase="SearchQueryTypes.caption" class="oi oi-image"></span>
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {Component, OnDestroy, TemplateRef} from '@angular/core';
|
||||
import {AutoCompleteService} from './autocomplete.service';
|
||||
import {AutoCompleteItem} from '../../../../../common/entities/AutoCompleteItem';
|
||||
import {AutoCompleteService, RenderableAutoCompleteItem} from './autocomplete.service';
|
||||
import {ActivatedRoute, Params, Router, RouterLink} from '@angular/router';
|
||||
import {GalleryService} from '../gallery.service';
|
||||
import {BehaviorSubject, Subscription} from 'rxjs';
|
||||
@ -20,9 +19,9 @@ import {SearchQueryParserService} from './search-query-parser.service';
|
||||
})
|
||||
export class GallerySearchComponent implements OnDestroy {
|
||||
|
||||
autoCompleteItems: AutoCompleteRenderItem[] = [];
|
||||
autoCompleteRenders: AutoCompleteRenderItem[] = [];
|
||||
public searchQueryDTO: SearchQueryDTO = <TextSearch>{type: SearchQueryTypes.any_text, text: ''};
|
||||
public rawSearchText: string;
|
||||
public rawSearchText = '';
|
||||
mouseOverAutoComplete = false;
|
||||
readonly SearchQueryTypes: typeof SearchQueryTypes;
|
||||
modalRef: BsModalRef;
|
||||
@ -32,7 +31,7 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
lastInstantSearch: ''
|
||||
};
|
||||
private readonly subscription: Subscription = null;
|
||||
private autocompleteItems: BehaviorSubject<AutoCompleteItem[]>;
|
||||
private autoCompleteItems: BehaviorSubject<RenderableAutoCompleteItem[]>;
|
||||
|
||||
constructor(private _autoCompleteService: AutoCompleteService,
|
||||
private _searchQueryParserService: SearchQueryParserService,
|
||||
@ -58,13 +57,16 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
get SearchHint() {
|
||||
if (!this.autocompleteItems ||
|
||||
!this.autocompleteItems.value || this.autocompleteItems.value.length === 0) {
|
||||
if (!this.autoCompleteItems ||
|
||||
!this.autoCompleteItems.value || this.autoCompleteItems.value.length === 0) {
|
||||
return this.rawSearchText;
|
||||
}
|
||||
const searchText = this.getAutocompleteToken();
|
||||
if (this.autocompleteItems.value[0].text.startsWith(searchText)) {
|
||||
return this.rawSearchText + this.autocompleteItems.value[0].text.substr(searchText.length);
|
||||
if (searchText === '') {
|
||||
return this.rawSearchText;
|
||||
}
|
||||
if (this.autoCompleteItems.value[0].queryHint.startsWith(searchText)) {
|
||||
return this.rawSearchText + this.autoCompleteItems.value[0].queryHint.substr(searchText.length);
|
||||
}
|
||||
return this.rawSearchText;
|
||||
}
|
||||
@ -105,7 +107,7 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
|
||||
public onFocusLost() {
|
||||
if (this.mouseOverAutoComplete === false) {
|
||||
this.autoCompleteItems = [];
|
||||
this.autoCompleteRenders = [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,6 +132,7 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
|
||||
onQueryChange() {
|
||||
this.rawSearchText = this._searchQueryParserService.stringify(this.searchQueryDTO);
|
||||
this.validateRawSearchText();
|
||||
}
|
||||
|
||||
validateRawSearchText() {
|
||||
@ -149,10 +152,19 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
this.rawSearchText = this.SearchHint;
|
||||
this.validateRawSearchText();
|
||||
}
|
||||
|
||||
applyAutoComplete(item: AutoCompleteRenderItem) {
|
||||
const token = this.getAutocompleteToken();
|
||||
this.rawSearchText = this.rawSearchText.substr(0, this.rawSearchText.length - token.length)
|
||||
+ item.queryHint;
|
||||
this.emptyAutoComplete();
|
||||
this.validateRawSearchText();
|
||||
}
|
||||
|
||||
private emptyAutoComplete() {
|
||||
this.autoCompleteItems = [];
|
||||
this.autoCompleteRenders = [];
|
||||
}
|
||||
|
||||
private async autocomplete(searchText: string) {
|
||||
@ -168,7 +180,13 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
|
||||
if (searchText.trim().length > 0) {
|
||||
try {
|
||||
this.autocompleteItems = this._autoCompleteService.autoComplete(searchText);
|
||||
if (this.autoCompleteItems) {
|
||||
this.autoCompleteItems.unsubscribe();
|
||||
}
|
||||
this.autoCompleteItems = this._autoCompleteService.autoComplete(searchText);
|
||||
this.autoCompleteItems.subscribe(() => {
|
||||
this.showSuggestions(this.autoCompleteItems.value, searchText);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@ -178,11 +196,11 @@ export class GallerySearchComponent implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private showSuggestions(suggestions: AutoCompleteItem[], searchText: string) {
|
||||
private showSuggestions(suggestions: RenderableAutoCompleteItem[], searchText: string) {
|
||||
this.emptyAutoComplete();
|
||||
suggestions.forEach((item: AutoCompleteItem) => {
|
||||
const renderItem = new AutoCompleteRenderItem(item.text, searchText, item.type);
|
||||
this.autoCompleteItems.push(renderItem);
|
||||
suggestions.forEach((item: RenderableAutoCompleteItem) => {
|
||||
const renderItem = new AutoCompleteRenderItem(item.text, searchText, item.type, item.queryHint);
|
||||
this.autoCompleteRenders.push(renderItem);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -192,8 +210,9 @@ class AutoCompleteRenderItem {
|
||||
public highLightText = '';
|
||||
public postText = '';
|
||||
public type: SearchQueryTypes;
|
||||
public queryHint: string;
|
||||
|
||||
constructor(public text: string, searchText: string, type: SearchQueryTypes) {
|
||||
constructor(public text: string, searchText: string, type: SearchQueryTypes, queryHint: string) {
|
||||
const preIndex = text.toLowerCase().indexOf(searchText.toLowerCase());
|
||||
if (preIndex > -1) {
|
||||
this.preText = text.substring(0, preIndex);
|
||||
@ -203,6 +222,7 @@ class AutoCompleteRenderItem {
|
||||
this.postText = text;
|
||||
}
|
||||
this.type = type;
|
||||
this.queryHint = queryHint;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user