1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-24 05:17:16 +02:00

Improving search modal UI including fixing search modal delete button event propagation #587

This commit is contained in:
Patrik J. Braun 2023-03-16 23:18:52 +01:00
parent 66d20e200f
commit b9b41eded9
5 changed files with 115 additions and 109 deletions

View File

@ -8,7 +8,7 @@
<hr> <hr>
<app-gallery-search-query-entry <app-gallery-search-query-entry
[(ngModel)]="searchQueryDTO" [(ngModel)]="searchQueryDTO"
(change)="onQueryChange()" (change)="onChange()"
(ngModelChange)="onChange()" (ngModelChange)="onChange()"
name="search-root" name="search-root"
(delete)="resetQuery()"> (delete)="resetQuery()">

View File

@ -1,24 +1,7 @@
import { import {Component, EventEmitter, forwardRef, Input, Output,} from '@angular/core';
Component, import {SearchQueryDTO, SearchQueryTypes, TextSearch,} from '../../../../../../common/entities/SearchQueryDTO';
EventEmitter, import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormControl, ValidationErrors, Validator,} from '@angular/forms';
forwardRef, import {SearchQueryParserService} from '../search-query-parser.service';
Input,
Output,
} from '@angular/core';
import {
SearchQueryDTO,
SearchQueryTypes,
TextSearch,
} from '../../../../../../common/entities/SearchQueryDTO';
import {
ControlValueAccessor,
UntypedFormControl,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
} from '@angular/forms';
import { SearchQueryParserService } from '../search-query-parser.service';
@Component({ @Component({
selector: 'app-gallery-search-query-builder', selector: 'app-gallery-search-query-builder',
@ -38,8 +21,7 @@ import { SearchQueryParserService } from '../search-query-parser.service';
], ],
}) })
export class GallerySearchQueryBuilderComponent export class GallerySearchQueryBuilderComponent
implements ControlValueAccessor, Validator implements ControlValueAccessor, Validator {
{
public searchQueryDTO: SearchQueryDTO = { public searchQueryDTO: SearchQueryDTO = {
type: SearchQueryTypes.any_text, type: SearchQueryTypes.any_text,
text: '', text: '',
@ -48,7 +30,8 @@ export class GallerySearchQueryBuilderComponent
@Input() placeholder = $localize`Search`; @Input() placeholder = $localize`Search`;
public rawSearchText = ''; public rawSearchText = '';
constructor(private searchQueryParserService: SearchQueryParserService) {} constructor(private searchQueryParserService: SearchQueryParserService) {
}
validateRawSearchText(): void { validateRawSearchText(): void {
try { try {
@ -66,21 +49,16 @@ export class GallerySearchQueryBuilderComponent
text: '', text: '',
type: SearchQueryTypes.any_text, type: SearchQueryTypes.any_text,
} as TextSearch; } as TextSearch;
}
onQueryChange(): void {
this.rawSearchText = this.searchQueryParserService.stringify(
this.searchQueryDTO
);
this.onChange(); this.onChange();
} }
validate(control: UntypedFormControl): ValidationErrors { validate(control: UntypedFormControl): ValidationErrors {
return { required: true }; return {required: true};
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
public onTouched(): void {} public onTouched(): void {
}
public writeValue(obj: any): void { public writeValue(obj: any): void {
this.searchQueryDTO = obj; this.searchQueryDTO = obj;
@ -98,12 +76,17 @@ export class GallerySearchQueryBuilderComponent
} }
public onChange(): void { public onChange(): void {
this.rawSearchText = this.searchQueryParserService.stringify(
this.searchQueryDTO
);
this.propagateChange(this.searchQueryDTO); this.propagateChange(this.searchQueryDTO);
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private propagateChange = (_: unknown): void => {}; private propagateChange = (_: unknown): void => {
};
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private propagateTouch = (_: unknown): void => {}; private propagateTouch = (_: unknown): void => {
};
} }

View File

@ -5,14 +5,3 @@
label { label {
margin-top: 0.3rem; margin-top: 0.3rem;
} }
.match-type {
font-size: 2rem;
margin-top: -0.8rem;
font-weight: bold;
cursor: pointer;
}
.match-type.exact-match {
color: #0069d9;
}

View File

@ -51,24 +51,31 @@
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="!IsListQuery"> <ng-container *ngIf="!IsListQuery">
<div class="col-md-3"> <div class="col-lg-4 col-xl-3">
<select <div class="input-group">
id="searchType" <select
name="searchType" id="searchType"
class="form-select" name="searchType"
[(ngModel)]="queryEntry.type" class="form-select"
(ngModelChange)="onChangeType()"> [(ngModel)]="queryEntry.type"
<option *ngFor="let opt of SearchQueryTypesEnum" [ngValue]="opt.key">{{opt.key | stringifySearchType}} (ngModelChange)="onChangeType()">
</option> <option *ngFor="let opt of SearchQueryTypesEnum" [ngValue]="opt.key">{{opt.key | stringifySearchType}}
</select> </option>
</div> </select>
<div class="col-11 col-md-8" *ngIf="IsTextQuery"> <select
id="negate"
name="negate"
class="form-select w-auto flex-grow-0"
title="Negate"
p18n-title
[(ngModel)]="SelectedMatchType"
(ngModelChange)="onChange()">
<option *ngFor="let mt of MatchingTypes" [ngValue]="mt">{{mt}}
</select>
</div>
</div>
<div class="col-10 col-lg" *ngIf="IsTextQuery">
<div class="input-group"> <div class="input-group">
<span title="exact match"
p18n-title
class="match-type"
(click)="toggleMatchType()"
[class.exact-match]="AsTextQuery.matchType === TextSearchQueryMatchTypes.exact_match">"</span>
<input <input
id="searchField" id="searchField"
name="searchField" name="searchField"
@ -79,15 +86,10 @@
(change)="onChange()" (change)="onChange()"
(ngModelChange)="onChange()" (ngModelChange)="onChange()"
type="text"/> type="text"/>
<span title="exact match"
p18n-title
class="match-type"
(click)="toggleMatchType()"
[class.exact-match]="AsTextQuery.matchType === TextSearchQueryMatchTypes.exact_match">"</span>
</div> </div>
</div> </div>
<ng-container [ngSwitch]="queryEntry.type"> <ng-container [ngSwitch]="queryEntry.type">
<div *ngSwitchCase="SearchQueryTypes.distance" class="col-11 col-md-8 "> <div *ngSwitchCase="SearchQueryTypes.distance" class="col-10 col-lg">
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<div class="input-group"> <div class="input-group">
@ -118,7 +120,7 @@
</div> </div>
</div> </div>
<!-- Range Search Query --> <!-- Range Search Query -->
<div *ngSwitchCase="SearchQueryTypes.from_date" class="col-11 col-md-8 d-flex"> <div *ngSwitchCase="SearchQueryTypes.from_date" class="col-10 col-lg d-flex">
<input id="from_date" <input id="from_date"
name="from_date" name="from_date"
title="From date" title="From date"
@ -129,7 +131,7 @@
class="form-control input-md rounded-2" class="form-control input-md rounded-2"
type="date"> type="date">
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.to_date" class="col-11 col-md-8 d-flex"> <div *ngSwitchCase="SearchQueryTypes.to_date" class="col-10 col-lg d-flex">
<input id="to_date" <input id="to_date"
name="to_date" name="to_date"
title="To date" title="To date"
@ -140,7 +142,7 @@
class="form-control input-md rounded-2" class="form-control input-md rounded-2"
type="date"> type="date">
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.min_rating" class="col-11 col-md-8 d-flex"> <div *ngSwitchCase="SearchQueryTypes.min_rating" class="col-10 col-lg d-flex">
<input id="minRating" <input id="minRating"
name="minRating" name="minRating"
title="Minimum Rating" title="Minimum Rating"
@ -153,7 +155,7 @@
(ngModelChange)="onChange()" (ngModelChange)="onChange()"
type="number"> type="number">
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.max_rating" class="col-11 col-md-8 d-flex"> <div *ngSwitchCase="SearchQueryTypes.max_rating" class="col-10 col-lg d-flex">
<input id="maxRating" <input id="maxRating"
name="maxRating" name="maxRating"
title="Maximum Rating" title="Maximum Rating"
@ -166,7 +168,7 @@
(ngModelChange)="onChange()" (ngModelChange)="onChange()"
type="number"> type="number">
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.min_resolution" class="col-11 col-md-8"> <div *ngSwitchCase="SearchQueryTypes.min_resolution" class="col-10 col-lg">
<div class="input-group"> <div class="input-group">
<input id="minResolution" <input id="minResolution"
name="minResolution" name="minResolution"
@ -182,7 +184,7 @@
</div> </div>
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.max_resolution" class="col-11 col-md-8"> <div *ngSwitchCase="SearchQueryTypes.max_resolution" class="col-10 col-lg">
<div class="input-group"> <div class="input-group">
<input id="maxResolution" <input id="maxResolution"
name="maxResolution" name="maxResolution"
@ -197,7 +199,7 @@
<span class="input-group-text">Mpx</span> <span class="input-group-text">Mpx</span>
</div> </div>
</div> </div>
<div *ngSwitchCase="SearchQueryTypes.orientation" class="col-11 col-md-8 d-flex"> <div *ngSwitchCase="SearchQueryTypes.orientation" class="col-10 col-lg d-flex">
<div class="input-group col-md-6"> <div class="input-group col-md-6">
<select class="form-select rounded-2" <select class="form-select rounded-2"
[(ngModel)]="AsOrientationQuery.landscape" [(ngModel)]="AsOrientationQuery.landscape"
@ -212,11 +214,13 @@
</div> </div>
</div> </div>
</ng-container> </ng-container>
<button [ngClass]="'btn-danger'" <div class="col-2 col-lg-1 align-self-center">
class="btn float-end col-1 align-self-center" <button [ngClass]="'btn-danger'"
(click)="deleteItem()"> class="btn w-auto float-end"
<span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span> (click)="deleteItem()">
</button> <span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span>
</button>
</div>
</ng-container> </ng-container>
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, forwardRef, Output } from '@angular/core'; import {Component, EventEmitter, forwardRef, Output} from '@angular/core';
import { import {
DistanceSearch, DistanceSearch,
ListSearchQueryTypes, ListSearchQueryTypes,
@ -12,15 +12,8 @@ import {
TextSearchQueryMatchTypes, TextSearchQueryMatchTypes,
TextSearchQueryTypes, TextSearchQueryTypes,
} from '../../../../../../common/entities/SearchQueryDTO'; } from '../../../../../../common/entities/SearchQueryDTO';
import { Utils } from '../../../../../../common/Utils'; import {Utils} from '../../../../../../common/Utils';
import { import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormControl, ValidationErrors, Validator,} from '@angular/forms';
ControlValueAccessor,
UntypedFormControl,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
} from '@angular/forms';
@Component({ @Component({
selector: 'app-gallery-search-query-entry', selector: 'app-gallery-search-query-entry',
@ -40,8 +33,7 @@ import {
], ],
}) })
export class GallerySearchQueryEntryComponent export class GallerySearchQueryEntryComponent
implements ControlValueAccessor, Validator implements ControlValueAccessor, Validator {
{
public queryEntry: SearchQueryDTO; public queryEntry: SearchQueryDTO;
public SearchQueryTypesEnum: { value: string; key: SearchQueryTypes }[]; public SearchQueryTypesEnum: { value: string; key: SearchQueryTypes }[];
public SearchQueryTypes = SearchQueryTypes; public SearchQueryTypes = SearchQueryTypes;
@ -93,7 +85,48 @@ export class GallerySearchQueryEntryComponent
} }
validate(control: UntypedFormControl): ValidationErrors { validate(control: UntypedFormControl): ValidationErrors {
return { required: true }; return {required: true};
}
get MatchingTypes(): string[] {
if (this.IsListQuery) {
return [];
}
if (this.IsTextQuery) {
return ['=~', '=', '!=', '!~'];
}
return ['=', '!=']; //normal negatable query
}
get SelectedMatchType(): string {
if (this.AsTextQuery.matchType !== TextSearchQueryMatchTypes.like) {
if (this.AsTextQuery.negate) {
return '!=';
} else {
return '=';
}
} else {
if (this.AsTextQuery.negate) {
return '!~';
} else {
return '=~';
}
}
}
set SelectedMatchType(value: string) {
if (this.IsListQuery) {
return;
}
this.AsTextQuery.negate = value.charAt(0) === '!';
if (!this.IsTextQuery) {
return;
}
if (value === '!~' || value === '=~') {
this.AsTextQuery.matchType = TextSearchQueryMatchTypes.like;
} else {
this.AsTextQuery.matchType = TextSearchQueryMatchTypes.exact_match;
}
} }
addQuery(): void { addQuery(): void {
@ -110,14 +143,14 @@ export class GallerySearchQueryEntryComponent
if (this.IsListQuery) { if (this.IsListQuery) {
delete this.AsTextQuery.text; delete this.AsTextQuery.text;
this.AsListQuery.list = this.AsListQuery.list || [ this.AsListQuery.list = this.AsListQuery.list || [
{ type: SearchQueryTypes.any_text, text: '' } as TextSearch, {type: SearchQueryTypes.any_text, text: ''} as TextSearch,
{ type: SearchQueryTypes.any_text, text: '' } as TextSearch, {type: SearchQueryTypes.any_text, text: ''} as TextSearch,
]; ];
} else { } else {
delete this.AsListQuery.list; delete this.AsListQuery.list;
} }
if (this.queryEntry.type === SearchQueryTypes.distance) { if (this.queryEntry.type === SearchQueryTypes.distance) {
this.AsDistanceQuery.from = { text: '' }; this.AsDistanceQuery.from = {text: ''};
this.AsDistanceQuery.distance = 1; this.AsDistanceQuery.distance = 1;
} else { } else {
delete this.AsDistanceQuery.from; delete this.AsDistanceQuery.from;
@ -136,12 +169,14 @@ export class GallerySearchQueryEntryComponent
this.delete.emit(); this.delete.emit();
} }
itemDeleted(i: number): void { itemDeleted(index: number): void {
this.AsListQuery.list.splice(i, 1); this.AsListQuery.list.splice(index, 1);
this.onChange();
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
public onTouched(): void {} public onTouched(): void {
}
public writeValue(obj: SearchQueryDTO): void { public writeValue(obj: SearchQueryDTO): void {
this.queryEntry = obj; this.queryEntry = obj;
@ -159,18 +194,13 @@ export class GallerySearchQueryEntryComponent
this.propagateChange(this.queryEntry); this.propagateChange(this.queryEntry);
} }
public toggleMatchType(): void {
this.AsTextQuery.matchType =
this.AsTextQuery.matchType === TextSearchQueryMatchTypes.exact_match
? TextSearchQueryMatchTypes.like
: TextSearchQueryMatchTypes.exact_match;
this.onChange();
}
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private propagateChange = (_: unknown): void => {}; private propagateChange = (_: unknown): void => {
};
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private propagateTouch = (_: unknown): void => {}; private propagateTouch = (_: unknown): void => {
};
} }